diff options
Diffstat (limited to 'engines/sci')
69 files changed, 3321 insertions, 2109 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index b20ed3f8be..8737c8b954 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -675,7 +675,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"); @@ -760,7 +760,7 @@ void Console::cmdDiskDumpWorker(ResourceType resourceType, int resourceNumber, u ResourceId resourceId; Resource *resource = NULL; char outFileName[50]; - + switch (resourceType) { case kResourceTypeAudio36: case kResourceTypeSync36: { @@ -877,7 +877,7 @@ bool Console::cmdList(int argc, const char **argv) { currentMap = map; displayCount = 0; } - + if (displayCount % 3 == 0) debugPrintf(" "); @@ -2075,10 +2075,6 @@ 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); - break; - case SEG_TYPE_BITMAP: debugPrintf("T SCI32 bitmaps (%d)", (*(BitmapTable *)mobj).entries_used); break; @@ -2179,7 +2175,7 @@ bool Console::segmentInfo(int nr) { for (uint i = 0; i < lt.size(); i++) if (lt.isValidEntry(i)) { debugPrintf(" [%04x]: ", i); - printList(<[i]); + printList(lt[i]); } } break; @@ -2210,9 +2206,6 @@ bool Console::segmentInfo(int nr) { break; #ifdef ENABLE_SCI32 - case SEG_TYPE_STRING: - debugPrintf("SCI32 strings\n"); - break; case SEG_TYPE_ARRAY: debugPrintf("SCI32 arrays\n"); break; @@ -2786,16 +2779,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"); @@ -2808,22 +2793,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); - 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); + printArray(reg); break; } case SEG_TYPE_BITMAP: { - debugPrintf("SCI32 bitmap:\n"); - const SciBitmap *bitmap = _engine->_gamestate->_segMan->lookupBitmap(reg); - Common::hexdump((const byte *) bitmap->getRawData(), bitmap->getRawSize(), 16, 0); + printBitmap(reg); break; } #endif @@ -2919,7 +2894,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; @@ -2941,7 +2916,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; @@ -2963,7 +2938,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; @@ -3778,7 +3753,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; } @@ -4373,8 +4348,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"); @@ -4384,8 +4379,7 @@ 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; } @@ -4394,16 +4388,15 @@ void Console::printList(List *list) { 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"); } @@ -4447,6 +4440,89 @@ 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 (%dx%d; res %dx%d; origin %dx%d; skip color %u; %s; %s):\n", + bitmap.getWidth(), bitmap.getHeight(), + bitmap.getXResolution(), bitmap.getYResolution(), + bitmap.getOrigin().x, bitmap.getOrigin().y, + bitmap.getSkipColor(), + bitmap.getRemap() ? "remap" : "no remap", + bitmap.getShouldGC() ? "GC" : "no GC"); + + 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); diff --git a/engines/sci/console.h b/engines/sci/console.h index cf85def950..0b87a4408b 100644 --- a/engines/sci/console.h +++ b/engines/sci/console.h @@ -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(); @@ -169,7 +173,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/detection.cpp b/engines/sci/detection.cpp index ad2b0f31a5..83f1d30916 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -772,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); @@ -795,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 { @@ -807,7 +805,7 @@ SaveStateDescriptor SciMetaEngine::querySaveMetaInfos(const char *target, int sl if (in) { SavegameMetadata meta; - + if (!get_savegame_metadata(in, &meta)) { // invalid delete in; diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index fafbf60d5a..22d2b6f308 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -866,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, GUIO_GK2 }, + 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" @@ -884,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, GUIO_GK2 }, + 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" @@ -902,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, GUIO_GK2 }, + 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: @@ -914,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, GUIO_GK2_MAC }, + Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_CD | ADGF_UNSTABLE, GUIO_GK2_MAC }, #endif // ENABLE_SCI32 @@ -2580,7 +2580,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "0c0804434ea62278dd15032b1947426c", 8872}, {"resource.000", 0, "9a9f4870504444cda863dd14d077a680", 18520872}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL6HIRES }, + 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 @@ -2588,7 +2588,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, GUIO_LSL6HIRES }, + 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 @@ -2596,7 +2596,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "d184e9aa4f2d4b5670ddb3669db82cda", 8896}, {"resource.000", 0, "bd944d2b06614a5b39f1586906f0ee88", 18538987}, AD_LISTEND}, - Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL6HIRES }, + Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL6HIRES }, #define GUIO_LSL7_DEMO GUIO5(GUIO_NOSPEECH, \ GUIO_NOASPECT, \ @@ -2624,7 +2624,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, GUIO_LSL7 }, + Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 }, // Larry 7 - German DOS (from Tobis87) // SCI interpreter version 3.000.000 @@ -2632,7 +2632,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, GUIO_LSL7 }, + 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 @@ -2640,7 +2640,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, GUIO_LSL7 }, + Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 }, // Larry 7 - Italian DOS CD (from glorifindel) // SCI interpreter version 3.000.000 @@ -2648,7 +2648,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, GUIO_LSL7 }, + 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" @@ -2656,7 +2656,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resmap.000", 0, "8f3d603e1acc834a5d598b30cdfc93f3", 8188}, {"ressci.000", 0, "32792f9bc1bf3633a88b382bb3f6e40d", 67071418}, AD_LISTEND}, - Common::ES_ESP, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL7 }, + Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 }, #endif #define GUIO_LIGHTHOUSE_DEMO GUIO5(GUIO_NOSPEECH, \ @@ -2696,7 +2696,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, GUIO_LIGHTHOUSE }, + 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" @@ -2706,7 +2706,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, GUIO_LIGHTHOUSE }, + 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" @@ -2716,7 +2716,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, GUIO_LIGHTHOUSE }, + Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LIGHTHOUSE }, #endif // ENABLE_SCI3_GAMES #endif // ENABLE_SCI32 @@ -2850,7 +2850,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "5159a1578c4306bfe070a3e4d8c2e1d3", 4741}, {"resource.000", 0, "1926925c95d82f0999590e93b02887c5", 15150768}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_MOTHERGOOSEHIRES }, + 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" @@ -2858,7 +2858,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, GUIO_MOTHERGOOSEHIRES }, + Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_MOTHERGOOSEHIRES }, #endif // ENABLE_SCI32 // Ms. Astro Chicken - English DOS @@ -2904,7 +2904,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resmap.007", 0, "aa8175cfc93242af6f5e65bdceaafc0d", 7972}, //{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 25859038}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA }, + 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" @@ -2924,7 +2924,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, GUIO_PHANTASMAGORIA }, + 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" @@ -2947,7 +2947,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, GUIO_PHANTASMAGORIA }, + Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA }, // Phantasmagoria - French DOS // Supplied by Kervala in bug #6574 @@ -2966,7 +2966,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, GUIO_PHANTASMAGORIA }, + Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA }, // Phantasmagoria - English DOS Demo // Executable scanning reports "2.100.002" @@ -2985,7 +2985,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, GUIO_PHANTASMAGORIA }, + 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: @@ -3001,7 +3001,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { // Data8-12 are empty {"Data13", 0, "6d2c450fca19a69b5af74ed5b03c0a17", 14923328}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA_MAC }, + Common::EN_ANY, Common::kPlatformMacintosh, ADGF_CD | ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA_MAC }, #ifdef ENABLE_SCI3_GAMES @@ -3368,7 +3368,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, GUIO_PQSWAT }, + 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" @@ -3383,7 +3383,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, GUIO_PQSWAT }, + 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) diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp index 8cecd8c82c..d46d68d9df 100644 --- a/engines/sci/engine/file.cpp +++ b/engines/sci/engine/file.cpp @@ -311,43 +311,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 diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h index 982d7b7823..0154338f6c 100644 --- a/engines/sci/engine/file.h +++ b/engines/sci/engine/file.h @@ -34,12 +34,22 @@ enum { _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 @@ -53,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 { diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp index 6c1713bed9..50f7709baf 100644 --- a/engines/sci/engine/gc.cpp +++ b/engines/sci/engine/gc.cpp @@ -46,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 diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp index c03504d277..85cad99226 100644 --- a/engines/sci/engine/kernel.cpp +++ b/engines/sci/engine/kernel.cpp @@ -410,7 +410,6 @@ 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; diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index 45477e1153..cce9a223d3 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -483,30 +483,46 @@ 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); @@ -547,7 +563,7 @@ 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 kBitmapSetDisplace(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); @@ -576,8 +592,6 @@ reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv); reg_t kListAllTrue(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 kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv); @@ -607,8 +621,6 @@ reg_t kMorphOn(EngineState *s, int argc, reg_t *argv); reg_t kText(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 kSave(EngineState *s, int argc, reg_t *argv); -reg_t kAutoSave(EngineState *s, int argc, reg_t *argv); reg_t kList(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); @@ -622,6 +634,7 @@ 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); @@ -707,7 +720,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 6e141e7f3b..0f1210e0e1 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -67,8 +67,8 @@ struct SciKernelMapSubEntry { #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_UNTIL_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY -#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE +#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 @@ -248,7 +248,7 @@ static const SciKernelMapSubEntry kDoAudio_subops[] = { // 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 }, @@ -311,9 +311,9 @@ static const SciKernelMapSubEntry kPalette_subops[] = { 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 }, @@ -326,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 }; @@ -351,15 +351,19 @@ static const SciKernelMapSubEntry kPalCycle_subops[] = { // 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 }; @@ -387,12 +391,12 @@ static const SciKernelMapSubEntry kBitmap_subops[] = { { 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(BitmapSetDisplace), "rii", 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_SCI21LATE, 12, MAP_CALL(BitmapGetInfo), "r(i)(i)", NULL }, - { SIG_SINCE_SCI21LATE, 13, MAP_CALL(BitmapScale), "r...ii", 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 }, @@ -501,44 +505,56 @@ static const SciKernelMapSubEntry kRemapColors_subops[] = { }; // 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*)", kStringPutAt_workarounds }, - // 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 }; @@ -613,10 +629,13 @@ static SciKernelMapEntry s_kernelMap[] = { { "CelHigh", kCelHigh32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, NULL }, { "CelWide", kCelWide32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, kCelWide_workarounds }, #endif - { MAP_CALL(CheckFreeSpace), SIG_SCI32, SIGFOR_ALL, "r.*", NULL, NULL }, + { 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 }, @@ -632,7 +651,7 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(DisposeWindow), SIG_EVERYWHERE, "i(i)", NULL, NULL }, { 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 }, + { "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 }, @@ -666,15 +685,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_SCI16, SIGFOR_ALL, "o", NULL, NULL }, #ifdef ENABLE_SCI32 { "GlobalToLocal", kGlobalToLocal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, #endif - { MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL }, + { 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 }, @@ -729,9 +756,15 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL }, { 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_SCI11, SIGFOR_ALL, "i(i)(i)(i)(iiiiii)", NULL, NULL }, { MAP_CALL(SetCursor), SIG_SCI16, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds }, @@ -805,13 +838,13 @@ static SciKernelMapEntry s_kernelMap[] = { #ifdef ENABLE_SCI32 // SCI2 Kernel Functions // TODO: whoever knows his way through those calls, fix the signatures. - { "TextSize", kTextSize32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)", NULL, NULL }, - { MAP_DUMMY(TextColors), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, - { MAP_DUMMY(TextFonts), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { "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 }, @@ -840,8 +873,8 @@ 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(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 }, @@ -879,9 +912,9 @@ 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_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { MAP_DUMMY(InvertRect), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, { MAP_DUMMY(InputText), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_CALL(TextWidth), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri", NULL, NULL }, + { MAP_CALL(TextWidth), SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri", NULL, NULL }, { MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // SCI2.1 Kernel Functions @@ -917,14 +950,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 @@ -957,6 +990,7 @@ 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 }, diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp index 9250e0fc13..a00630622e 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -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) { @@ -236,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); } @@ -369,6 +369,30 @@ reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) { 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 e8b9d0461d..6aad256664 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -51,6 +51,7 @@ 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. @@ -158,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) { @@ -224,6 +229,45 @@ reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) { // ---- 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()); @@ -233,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; + } + + int mode = argv[1].toUint16(); bool unwrapFilename = true; // SQ4 floppy prepends /\ to the filenames @@ -253,94 +301,158 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { return SIGNAL_REG; } - // Torin's autosave system checks for the presence of autosave.cat - // by opening it. Since we don't use .cat files, we instead check - // for autosave.000 or autosave.001. - // - // The same logic is being followed for torinsg.cat - this shows - // the "Open..." button when continuing a game. - // - // This has the added benefit of not detecting an SSCI autosave.cat - // accompanying SSCI autosave files that we wouldn't be able to load. - if (g_sci->getGameId() == GID_TORIN && (name == "autosave.cat" || name == "torinsg.cat")) { - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - const Common::String pattern = (name == "autosave.cat") ? g_sci->wrapFilename("autosave.###") : g_sci->getSavegamePattern(); - bool exists = !saveFileMan->listSavefiles(pattern).empty(); - if (exists) { - // Dummy handle. Torin only checks if this is SIGNAL_REG, - // and calls kFileIOClose on it. - return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); - } else { - return SIGNAL_REG; +#ifdef ENABLE_SCI32 + // 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 += "."; } } - 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 (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) { + // 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 saveNo; + sscanf(name.c_str(), "%d.SG", &saveNo); + saveNo += kSaveIdShift; + + SavegameDesc save; + fillSavegameDesc(g_sci->getSavegameName(saveNo), &save); + + Common::String score; + if (!save.highScore) { + score = Common::String::format("%u", save.lowScore); + } else { + score = Common::String::format("%u%03u", save.highScore, save.lowScore); + } - 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); - } + 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()); -#ifdef ENABLE_SCI32 - // 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")) { + 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) { - // Game scripts are trying to create a file with the save - // description, stop them here + // 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 slotNumber; - sscanf(name.c_str(), "%d.SG", &slotNumber); + int saveNo; + sscanf(name.c_str(), "%d.DTA", &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); - int size = strlen(saves[savegameNr].name) + 2; - char *buf = (char *)malloc(size); - strcpy(buf, saves[savegameNr].name); - buf[size - 1] = 0; // Spot description (empty) + 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); - uint handle = findFreeFileHandle(s); + const uint handle = findFreeFileHandle(s); - s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buf, size, DisposeAfterUse::YES); + 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 @@ -363,7 +475,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) { 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); @@ -371,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) @@ -403,18 +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) { - f->_out->write(buf, size); - success = true; + bytesWritten = f->_out->write(buf, size); + success = !f->_out->err(); } delete[] buf; + +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + if (!success) { + return SIGNAL_REG; + } + + return make_reg(0, bytesWritten); + } +#endif + if (success) return NULL_REG; return make_reg(0, 6); // DOS - invalid handle @@ -444,19 +575,35 @@ 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); } +#endif } else { const Common::String wrappedName = g_sci->wrapFilename(name); result = saveFileMan->removeSavefile(wrappedName); } 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 @@ -567,6 +714,12 @@ 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 @@ -662,7 +815,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) { @@ -676,27 +829,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) { @@ -721,6 +860,13 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv) { #endif reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { + // 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; @@ -736,13 +882,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } - // Torin has two sets of saves: autosave.### and torinsg.###, both with - // their own slots and .cat file. - // The autosave system uses autosave.000 and autosave.001. - // It also checks the presence of autosave.cat to determine if it should - // show the chapter selection menu on startup. (See kFileIOOpen.) - bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave"; - if (argv[0].isNull()) { // Direct call, from a patched Game::save if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull())) @@ -761,14 +900,6 @@ 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 if (torinAutosave) { - if (argv[2].isNull()) - error("kSaveGame: called with description being NULL"); - game_description = s->_segMan->getString(argv[2]); - savegameId = virtualId; - - debug(3, "kSaveGame(%s,%d,%s,%s) [Torin autosave]", game_id.c_str(), virtualId, game_description.c_str(), version.c_str()); } else { // Real call from script if (argv[2].isNull()) @@ -844,10 +975,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); Common::OutSaveFile *out; - if (torinAutosave) { - filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId)); - } - out = saveFileMan->openForSaving(filename); if (!out) { warning("Error opening savegame \"%s\" for writing", filename.c_str()); @@ -876,10 +1003,6 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId); - - // See comment in kSaveGame - bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave"; - if (argv[0].isNull()) { // Direct call, either from launcher or from a patched Game::restore if (savegameId == -1) { @@ -895,7 +1018,7 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { pausedMusic = true; } // don't adjust ID of the saved game, it's already correct - } else if (!torinAutosave) { + } else { if (g_sci->getGameId() == GID_JONES) { // Jones has one save slot only savegameId = 0; @@ -912,9 +1035,8 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { s->r_acc = NULL_REG; // signals success Common::Array<SavegameDesc> saves; - if (!torinAutosave) - listSavegames(saves); - if (!torinAutosave && findSavegame(saves, savegameId) == -1) { + listSavegames(saves); + if (findSavegame(saves, savegameId) == -1) { s->r_acc = TRUE_REG; warning("Savegame ID %d not found", savegameId); } else { @@ -922,10 +1044,6 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { Common::String filename = g_sci->getSavegameName(savegameId); Common::SeekableReadStream *in; - if (torinAutosave) { - filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId)); - } - in = saveFileMan->openForLoading(filename); if (in) { // found a savegame file @@ -998,10 +1116,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; @@ -1010,6 +1124,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) { @@ -1037,46 +1155,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]); + } - 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; + 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; + + 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; + } + + if (save.version < MINIMUM_SAVEGAME_VERSION || + save.version > CURRENT_SAVEGAME_VERSION || + save.gameVersion != gameVersion) { -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) + 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 d375a27954..c605ba29e1 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -356,7 +356,7 @@ 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); @@ -832,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: @@ -1191,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); diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index a33fcf3167..e5b8da4620 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -75,17 +75,14 @@ reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv) { CelObjView celObj(viewId, loopNo, celNo); const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; - const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - - const Ratio scaleX(scriptWidth, celObj._scaledWidth); - const Ratio scaleY(scriptHeight, celObj._scaledHeight); + const Ratio scaleX(scriptWidth, celObj._xResolution); int16 brLeft; if (celObj._mirrorX) { - brLeft = x - ((celObj._width - celObj._displace.x) * scaleX).toInt(); + brLeft = x - ((celObj._width - celObj._origin.x) * scaleX).toInt(); } else { - brLeft = x - (celObj._displace.x * scaleX).toInt(); + brLeft = x - (celObj._origin.x * scaleX).toInt(); } const int16 brRight = brLeft + (celObj._width * scaleX).toInt() - 1; @@ -317,7 +314,7 @@ reg_t kText(EngineState *s, int argc, reg_t *argv) { reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxText32->setFont(argv[2].toUint16()); - reg_t *rect = s->_segMan->derefRegPtr(argv[0], 4); + SciArray *rect = s->_segMan->lookupArray(argv[0]); if (rect == nullptr) { error("kTextSize: %04x:%04x cannot be dereferenced", PRINT_REG(argv[0])); } @@ -327,10 +324,14 @@ reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) { bool doScaling = argc > 4 ? argv[4].toSint16() : true; Common::Rect textRect = g_sci->_gfxText32->getTextSize(text, maxWidth, doScaling); - rect[0] = make_reg(0, textRect.left); - rect[1] = make_reg(0, textRect.top); - rect[2] = make_reg(0, textRect.right - 1); - rect[3] = make_reg(0, textRect.bottom - 1); + + 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; } @@ -426,7 +427,7 @@ reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv) { 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._scaledHeight))); + return make_reg(0, mulru(celObj._height, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, celObj._yResolution))); } reg_t kCelWide32(EngineState *s, int argc, reg_t *argv) { @@ -434,7 +435,7 @@ reg_t kCelWide32(EngineState *s, int argc, reg_t *argv) { 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._scaledWidth))); + 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) { @@ -447,10 +448,10 @@ reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 0: - result = view._displace.x; + result = view._origin.x; break; case 1: - result = view._displace.y; + result = view._origin.y; break; case 2: case 3: @@ -615,13 +616,13 @@ reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv) { // 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->_scaledHeight = (g_sci->_gfxText32->_font->getHeight() * g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight + g_sci->_gfxText32->_scaledHeight - 1) / g_sci->_gfxText32->_scaledHeight; - return make_reg(0, g_sci->_gfxText32->_scaledHeight); + 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->_scaledWidth = argv[0].toUint16(); - g_sci->_gfxText32->_scaledHeight = argv[1].toUint16(); + g_sci->_gfxText32->_xResolution = argv[0].toUint16(); + g_sci->_gfxText32->_yResolution = argv[1].toUint16(); return s->r_acc; } @@ -636,12 +637,12 @@ reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv) { int16 height = argv[1].toSint16(); int16 skipColor = argv[2].toSint16(); int16 backColor = argv[3].toSint16(); - int16 scaledWidth = argc > 4 ? argv[4].toSint16() : g_sci->_gfxText32->_scaledWidth; - int16 scaledHeight = argc > 5 ? argv[5].toSint16() : g_sci->_gfxText32->_scaledHeight; + 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, scaledWidth, scaledHeight, 0, useRemap, true); + SciBitmap &bitmap = *s->_segMan->allocateBitmap(&bitmapId, width, height, skipColor, 0, 0, xResolution, yResolution, 0, useRemap, true); memset(bitmap.getPixels(), backColor, width * height); return bitmapId; } @@ -675,12 +676,12 @@ reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv) { const int16 alignY = argc > 8 ? argv[8].toSint16() : -1; Common::Point position( - x == -1 ? bitmap.getDisplace().x : x, - y == -1 ? bitmap.getDisplace().y : y + x == -1 ? bitmap.getOrigin().x : x, + y == -1 ? bitmap.getOrigin().y : y ); - position.x -= alignX == -1 ? view._displace.x : alignX; - position.y -= alignY == -1 ? view._displace.y : alignY; + position.x -= alignX == -1 ? view._origin.x : alignX; + position.y -= alignY == -1 ? view._origin.y : alignY; Common::Rect drawRect( position.x, @@ -755,16 +756,37 @@ reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv) { return kStubNull(s, argc + 1, argv - 1); } -reg_t kBitmapSetDisplace(EngineState *s, int argc, reg_t *argv) { +reg_t kBitmapSetOrigin(EngineState *s, int argc, reg_t *argv) { SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]); - bitmap.setDisplace(Common::Point(argv[1].toSint16(), argv[2].toSint16())); + bitmap.setOrigin(Common::Point(argv[1].toSint16(), argv[2].toSint16())); return s->r_acc; } reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv) { - // viewId, loopNo, celNo, skipColor, backColor, useRemap, source overlay bitmap + 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; - return kStub(s, argc + 1, argv - 1); + 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)); + } + + SciArray &clut = *s->_segMan->lookupArray(clutHandle); + bitmap.applyRemap(clut); + } + + return bitmapId; } reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv) { @@ -780,12 +802,24 @@ reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv) { } reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv) { - // bitmap + SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]); - // argc 1 = get width - // argc 2 = pixel at row 0 col n - // argc 3 = pixel at row n col n - return kStub(s, argc + 1, argv - 1); + if (argc == 1) { + return make_reg(0, bitmap.getWidth()); + } + + 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) { @@ -924,17 +958,17 @@ reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv) { } 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 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; @@ -950,9 +984,9 @@ reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) { } 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); + 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; } @@ -966,31 +1000,31 @@ reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv) { } 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 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()); } @@ -1006,7 +1040,6 @@ reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv) { 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; } @@ -1014,7 +1047,6 @@ reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv) { 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; } diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp index e780d3cdc3..5cd9c3623d 100644 --- a/engines/sci/engine/klists.cpp +++ b/engines/sci/engine/klists.cpp @@ -554,7 +554,7 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) { ++list->numRecursions; - if (list->numRecursions > ARRAYSIZE(list->nextNodes)) { + if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) { error("Too much recursion in kListEachElementDo"); } @@ -683,197 +683,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 (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - if (callStringFunc) { - Kernel *kernel = g_sci->getKernel(); - uint16 kernelStringFuncId = kernel->_kernelFunc_StringId; - if (kernelStringFuncId) { - const KernelFunction *kernelStringFunc = &kernel->_kernelFuncs[kernelStringFuncId]; - - 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()); + if (type == kArrayTypeString) { + ++size; } - 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()); - } - 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"); - } - } + const uint16 sourceIndex = argv[3].toUint16(); + const uint16 count = argv[4].toUint16(); - 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(); - - 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 f2a3c6b0f7..9aa03a4760 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -459,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 diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index 06f16299aa..937b1cfc2f 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -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 diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp index 6fd130bceb..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); diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 1c08bf597c..6ec61343b0 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 */ @@ -661,238 +643,236 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 -// TODO: there is an unused second argument, happens at least in LSL6 right during the intro +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 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()); -} - -// 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]); +reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv) { + const uint16 index = argv[1].toUint16(); - 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 = string2Size - index2; - if (argv[4].toSint16() != -1) { - count = MIN(count, (uint32)argv[4].toUint16()); + bool isWidth(const char c) { + return strchr("0123456789*", c); } -// reg_t strAddress = argv[0]; - 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) { + return type == 'd' || type == 'i'; + } - return argv[0]; -} + bool isUnsignedType(const char type) { + return strchr("uxXoc", 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)); -} + assert(argIndex < argc); + out += readPlaceholder(in, argv[argIndex++]); + } 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 b539c84f5d..83a02883af 100644 --- a/engines/sci/engine/kvideo.cpp +++ b/engines/sci/engine/kvideo.cpp @@ -165,7 +165,6 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { 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. @@ -180,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; @@ -253,11 +254,13 @@ reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv) { Common::Rect frameRect; const uint16 numFramesTotal = g_sci->_video32->getRobotPlayer().getFrameSize(frameRect); - reg_t *outRect = s->_segMan->derefRegPtr(argv[0], 4); - outRect[0] = make_reg(0, frameRect.left); - outRect[1] = make_reg(0, frameRect.top); - outRect[2] = make_reg(0, frameRect.right - 1); - outRect[3] = make_reg(0, frameRect.bottom - 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); return make_reg(0, numFramesTotal); } @@ -446,10 +449,10 @@ reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv) { 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(scriptWidth, (int16)(argv[2].toSint16() + 1)); - blackoutArea.bottom = MIN(scriptHeight, (int16)(argv[3].toSint16() + 1)); + 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; } diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp index 5300b72b71..b0615b4213 100644 --- a/engines/sci/engine/message.cpp +++ b/engines/sci/engine/message.cpp @@ -439,21 +439,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/savegame.cpp b/engines/sci/engine/savegame.cpp index be2d7660cb..2e4da46b70 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -102,66 +102,6 @@ void syncWithSerializer(Common::Serializer &s, Node &obj) { syncWithSerializer(s, obj.value); } -#ifdef ENABLE_SCI32 -void syncWithSerializer(Common::Serializer &s, SciArray<reg_t> &obj) { - 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); - } -} - -void syncWithSerializer(Common::Serializer &s, SciString &obj) { - 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 - #pragma mark - // By default, sync using syncWithSerializer, which in turn can easily be overloaded. @@ -292,9 +232,6 @@ 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 @@ -393,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) { @@ -673,13 +632,16 @@ 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); @@ -707,11 +669,35 @@ void ArrayTable::saveLoadWithSerializer(Common::Serializer &ser) { sync_Table<ArrayTable>(ser, *this); } -void StringTable::saveLoadWithSerializer(Common::Serializer &ser) { - if (ser.getVersion() < 18) - return; +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); + } - sync_Table<StringTable>(ser, *this); + 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) { @@ -784,7 +770,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); @@ -794,7 +780,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); @@ -815,6 +801,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); @@ -1044,10 +1040,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); diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index 6616081a20..873394aebb 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -37,6 +37,7 @@ 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 @@ -63,7 +64,7 @@ struct EngineState; */ enum { - CURRENT_SAVEGAME_VERSION = 38, + CURRENT_SAVEGAME_VERSION = 39, MINIMUM_SAVEGAME_VERSION = 14 }; @@ -77,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_patches.cpp b/engines/sci/engine/script_patches.cpp index 664e7fa0c4..f2f290fa9e 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. @@ -614,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 @@ -918,9 +1003,25 @@ static const SciScriptPatcherEntry gk1Signatures[] = { { 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 @@ -1618,6 +1719,10 @@ 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 @@ -1753,7 +1858,7 @@ static const uint16 kq7SignatureSubtitleFix3[] = { static const uint16 kq7PatchSubtitleFix3[] = { PATCH_ADDTOOFFSET(+2), // skip over "pToa initialized code" 0x2f, 0x0c, // bt [skip init code] - saved 1 byte - 0x38, + 0x38, PATCH_GETORIGINALBYTE(+6), PATCH_GETORIGINALBYTE(+7), // pushi (init) 0x76, // push0 @@ -1779,6 +1884,8 @@ static const SciScriptPatcherEntry kq7Signatures[] = { 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. @@ -2079,6 +2186,20 @@ static const SciScriptPatcherEntry larry6Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Leisure Suit Larry 6 Hires + +// script, description, signature patch +static const SciScriptPatcherEntry larry6HiresSignatures[] = { + { 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 // @@ -2700,6 +2821,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 @@ -2841,6 +2974,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. @@ -3745,7 +3892,7 @@ static const uint16 qfg3PatchRoom750Bounds1[] = { static const uint16 qfg3SignatureRoom750Bounds2[] = { // (ego x: 294 y: 39) 0x78, // push1 ("x") - 0x78, // push1 + 0x78, // push1 0x38, SIG_UINT16(294), // pushi 294 0x76, // push0 ("y") 0x78, // push1 @@ -3808,6 +3955,20 @@ static const SciScriptPatcherEntry qfg3Signatures[] = { 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 @@ -4371,6 +4532,34 @@ static const SciScriptPatcherEntry sq5Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Space Quest 6 + +// script, description, signature patch +static const SciScriptPatcherEntry sq6Signatures[] = { + { true, 15, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 22, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 460, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { 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() { @@ -4802,18 +4991,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; @@ -4832,12 +5028,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; @@ -4850,6 +5061,11 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_QFG3: signatureTable = qfg3Signatures; break; +#ifdef ENABLE_SCI32 + case GID_QFG4: + signatureTable = qfg4Signatures; + break; +#endif case GID_SQ1: signatureTable = sq1vgaSignatures; break; @@ -4859,6 +5075,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/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index 5cf8d6162d..83e7495b3c 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -42,7 +42,6 @@ SegManager::SegManager(ResourceManager *resMan, ScriptPatcher *scriptPatcher) #ifdef ENABLE_SCI32 _arraysSegId = 0; - _stringSegId = 0; _bitmapSegId = 0; #endif @@ -72,7 +71,6 @@ void SegManager::resetSegMan() { #ifdef ENABLE_SCI32 _arraysSegId = 0; - _stringSegId = 0; _bitmapSegId = 0; #endif @@ -88,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 @@ -863,7 +860,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; @@ -875,10 +875,14 @@ SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) { offset = table->allocEntry(); *addr = make_reg(_arraysSegId, offset); - return &table->at(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)); @@ -899,54 +903,17 @@ void SegManager::freeArray(reg_t addr) { if (!arrayTable.isValidEntry(addr.getOffset())) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); - arrayTable[addr.getOffset()].destroy(); arrayTable.freeEntry(addr.getOffset()); } -SciString *SegManager::allocateString(reg_t *addr) { - StringTable *table; - int offset; - - if (!_stringSegId) { - table = (StringTable *)allocSegment(new StringTable(), &(_stringSegId)); - } else - table = (StringTable *)_heap[_stringSegId]; - - offset = table->allocEntry(); - - *addr = make_reg(_stringSegId, offset); - return &table->at(offset); -} - -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)); - - StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()]; - - if (!stringTable.isValidEntry(addr.getOffset())) - error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - - return &(stringTable[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)); - - StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()]; - - if (!stringTable.isValidEntry(addr.getOffset())) - error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); - - stringTable[addr.getOffset()].destroy(); - stringTable.freeEntry(addr.getOffset()); +bool SegManager::isArray(reg_t addr) const { + return addr.getSegment() == _arraysSegId; } #pragma mark - #pragma mark Bitmaps -SciBitmap *SegManager::allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 paletteSize, const bool remap, const bool gc) { +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; @@ -961,7 +928,7 @@ SciBitmap *SegManager::allocateBitmap(reg_t *addr, const int16 width, const int1 *addr = make_reg(_bitmapSegId, offset); SciBitmap &bitmap = table->at(offset); - bitmap.create(width, height, skipColor, displaceX, displaceY, scaledWidth, scaledHeight, paletteSize, remap, gc); + bitmap.create(width, height, skipColor, originX, originY, xResolution, yResolution, paletteSize, remap, gc); return &bitmap; } diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h index 8ed1c3a143..c409744711 100644 --- a/engines/sci/engine/seg_manager.h +++ b/engines/sci/engine/seg_manager.h @@ -433,16 +433,12 @@ 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); + bool isArray(reg_t addr) const; - SciString *allocateString(reg_t *addr); - SciString *lookupString(reg_t addr); - void freeString(reg_t addr); - SegmentId getStringSegmentId() { return _stringSegId; } - - SciBitmap *allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor = kDefaultSkipColor, const int16 displaceX = 0, const int16 displaceY = 0, const int16 scaledWidth = kLowResX, const int16 scaledHeight = kLowResY, const uint32 paletteSize = 0, const bool remap = false, const bool gc = true); + 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 @@ -469,7 +465,6 @@ private: #ifdef ENABLE_SCI32 SegmentId _arraysSegId; - SegmentId _stringSegId; SegmentId _bitmapSegId; #endif diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp index 7943946ee4..fffa7f4d7e 100644 --- a/engines/sci/engine/segment.cpp +++ b/engines/sci/engine/segment.cpp @@ -67,9 +67,6 @@ SegmentObj *SegmentObj::createSegmentObj(SegmentType type) { case SEG_TYPE_ARRAY: mem = new ArrayTable(); break; - case SEG_TYPE_STRING: - mem = new StringTable(); - break; case SEG_TYPE_BITMAP: mem = new BitmapTable(); break; @@ -254,63 +251,39 @@ SegmentRef DynMem::dereference(reg_t pointer) { SegmentRef ArrayTable::dereference(reg_t pointer) { SegmentRef ret; - ret.isRaw = false; - ret.maxSize = at(pointer.getOffset()).getSize() * 2; - ret.reg = at(pointer.getOffset()).getRawData(); - return ret; -} -void ArrayTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) { - at(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 = &at(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 = at(pointer.getOffset()).getSize(); - ret.raw = (byte *)at(pointer.getOffset()).getRawData(); - return ret; + return refs; } #endif diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index add5f4c57c..361c1cb895 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,7 @@ 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 @@ -408,142 +408,456 @@ 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; } + } - // So, we're going to have to create an array of some sort - T *newArray = new T[size]; - memset(newArray, 0, size * sizeof(T)); + /** + * Shrinks a string array to its optimal size. + */ + void snug() { + assert(_type == kArrayTypeString || _type == kArrayTypeByte); + resize(strlen((char *)_data) + 1, true); + } - // Check if we never created an array before - if (!_data) { - _size = _actualSize = size; - _data = newArray; - return; + /** + * 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); } - // Copy data from the old array to the new - memcpy(newArray, _data, _size * sizeof(T)); + switch(_type) { + case kArrayTypeInt16: + case kArrayTypeID: + return ((reg_t *)_data)[index]; + case kArrayTypeByte: + case kArrayTypeString: + return make_reg(0, ((byte *)_data)[index]); + 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); + } - // 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 *)_data)[index] = value; + break; + case kArrayTypeByte: + case kArrayTypeString: + ((byte *)_data)[index] = value.toSint16(); + break; + default: + error("Invalid array type %d", _type); + } } - T getValue(uint16 index) const { - if (index >= _size) - error("SciArray::getValue(): %d is out of bounds (%d)", index, _size); + /** + * Gets the value at the given index as an int16. + */ + int16 getAsInt16(const uint16 index) { + assert(_type == kArrayTypeInt16); - return _data[index]; + 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(); } - void setValue(uint16 index, T value) { - if (index >= _size) - error("SciArray::setValue(): %d is out of bounds (%d)", index, _size); + /** + * 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); + } - _data[index] = value; + ((reg_t *)_data)[index] = make_reg(0, value); } - byte getType() const { return _type; } - uint32 getSize() const { return _size; } - T *getRawData() { return _data; } - const T *getRawData() const { return _data; } + /** + * 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); -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 -}; + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } -class SciString : public SciArray<char> { -public: - SciString() : SciArray<char>() { setType(3); } + return ((byte *)_data)[index]; + } - // We overload destroy to ensure the string type is 3 after destroying - void destroy() { SciArray<char>::destroy(); _type = 3; } + /** + * 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); - Common::String toString() const; - void fromString(const Common::String &string); -}; + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } -struct ArrayTable : public SegmentObjTable<SciArray<reg_t> > { - ArrayTable() : SegmentObjTable<SciArray<reg_t> >(SEG_TYPE_ARRAY) {} + return ((char *)_data)[index]; + } - virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr); - virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; + /** + * 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); - void saveLoadWithSerializer(Common::Serializer &ser); - SegmentRef dereference(reg_t pointer); -}; + if (getSciVersion() >= SCI_VERSION_3) { + resize(index); + } else { + assert(index < _size); + } -struct StringTable : public SegmentObjTable<SciString> { - StringTable() : SegmentObjTable<SciString>(SEG_TYPE_STRING) {} + return ((reg_t *)_data)[index]; + } - virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { - at(sub_addr.getOffset()).destroy(); - freeEntry(sub_addr.getOffset()); + /** + * 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); + } + } + + /** + * 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; + } + + if (!count) { + return; + } + + resize(index + count); + + 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"); + } + } + + /** + * 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); + + 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"); } + /** + * 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'; + } + + 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); + } + } + } + } + + /** + * 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); + } + + /** + * 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); + } + +protected: + void *_data; + SciArrayType _type; + uint16 _size; + uint8 _elementSize; +}; + +struct ArrayTable : public SegmentObjTable<SciArray> { + ArrayTable() : SegmentObjTable<SciArray>(SEG_TYPE_ARRAY) {} + + virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const; + void saveLoadWithSerializer(Common::Serializer &ser); SegmentRef dereference(reg_t pointer); }; @@ -649,7 +963,7 @@ public: /** * Allocates and initialises a new bitmap. */ - inline void create(const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 paletteSize, const bool remap, const bool gc) { + 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); @@ -659,7 +973,7 @@ public: setWidth(width); setHeight(height); - setDisplace(Common::Point(displaceX, displaceY)); + setOrigin(Common::Point(originX, originY)); setSkipColor(skipColor); _data[9] = 0; WRITE_SCI11ENDIAN_UINT16(_data + 10, 0); @@ -670,8 +984,8 @@ public: setDataOffset(bitmapHeaderSize); setUncompressedDataOffset(bitmapHeaderSize); setControlOffset(0); - setScaledWidth(scaledWidth); - setScaledHeight(scaledHeight); + setXResolution(xResolution); + setYResolution(yResolution); _buffer = Buffer(getWidth(), getHeight(), getPixels()); } @@ -703,16 +1017,16 @@ public: BITMAP_PROPERTY(16, Width, 0); BITMAP_PROPERTY(16, Height, 2); - inline Common::Point getDisplace() const { + inline Common::Point getOrigin() const { return Common::Point( (int16)READ_SCI11ENDIAN_UINT16(_data + 4), (int16)READ_SCI11ENDIAN_UINT16(_data + 6) ); } - inline void setDisplace(const Common::Point &displace) { - WRITE_SCI11ENDIAN_UINT16(_data + 4, (uint16)displace.x); - WRITE_SCI11ENDIAN_UINT16(_data + 6, (uint16)displace.y); + 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 { @@ -761,7 +1075,7 @@ public: // NOTE: This property always seems to be zero BITMAP_PROPERTY(32, ControlOffset, 32); - inline uint16 getScaledWidth() const { + inline uint16 getXResolution() const { if (getDataOffset() >= 40) { return READ_SCI11ENDIAN_UINT16(_data + 36); } @@ -770,13 +1084,13 @@ public: return 320; } - inline void setScaledWidth(uint16 scaledWidth) { + inline void setXResolution(uint16 xResolution) { if (getDataOffset() >= 40) { - WRITE_SCI11ENDIAN_UINT16(_data + 36, scaledWidth); + WRITE_SCI11ENDIAN_UINT16(_data + 36, xResolution); } } - inline uint16 getScaledHeight() const { + inline uint16 getYResolution() const { if (getDataOffset() >= 40) { return READ_SCI11ENDIAN_UINT16(_data + 38); } @@ -785,9 +1099,9 @@ public: return 200; } - inline void setScaledHeight(uint16 scaledHeight) { + inline void setYResolution(uint16 yResolution) { if (getDataOffset() >= 40) { - WRITE_SCI11ENDIAN_UINT16(_data + 38, scaledHeight); + WRITE_SCI11ENDIAN_UINT16(_data + 38, yResolution); } } @@ -803,6 +1117,16 @@ public: } 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; + } + } }; struct BitmapTable : public SegmentObjTable<SciBitmap> { diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index ac621f58ae..3ae902215c 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -226,15 +226,14 @@ 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 { + 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()); + } else { *address.getPointer(segMan) = value; #ifdef ENABLE_SCI32 updateInfoFlagViewVisible(segMan->getObject(object), selectorId); @@ -255,12 +254,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++) @@ -288,8 +287,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/state.cpp b/engines/sci/engine/state.cpp index a338beffc9..c23add1211 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -159,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() { @@ -202,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); @@ -236,7 +236,7 @@ Common::String SciEngine::getSciLanguageString(const Common::String &str, kLangu while (1) { curChar = *textPtr; - + switch (curChar) { case 0: // Terminator NUL return fullWidth; @@ -257,7 +257,7 @@ Common::String SciEngine::getSciLanguageString(const Common::String &str, kLangu continue; } } - + textPtr++; mappedChar = s_halfWidthSJISMap[curChar]; @@ -380,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 baa912b60e..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); @@ -209,6 +220,11 @@ public: * 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 548fd477bf..82de957387 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,21 +125,17 @@ 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); @@ -179,7 +176,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 +197,9 @@ static void write_var(EngineState *s, int type, int index, reg_t value) { s->variables[type][index] = value; - if (type == VAR_GLOBAL && index == 90) { + 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. @@ -213,6 +210,12 @@ static void write_var(EngineState *s, int type, int index, reg_t value) { g_sci->updateScummVMAudioOptions(); } } + +#ifdef ENABLE_SCI32 + if (type == VAR_GLOBAL && index == kGlobalVarTextSpeed && getSciVersion() >= SCI_VERSION_2) { + ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8); + } +#endif } } @@ -361,16 +364,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 @@ -429,7 +429,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: { @@ -438,15 +438,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 diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index c41060dc32..dd66d9d52c 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -136,6 +136,22 @@ 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 + kGlobalVarShivers1Score = 349 +}; + /** Number of kernel calls in between gcs; should be < 50000 */ enum { GC_INTERVAL = 0x8000 diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp index d74e2b194c..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); } diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 7aaea0902a..4c0fee49c5 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -318,15 +318,15 @@ 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 @@ -347,6 +347,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 @@ -379,6 +380,7 @@ 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) @@ -396,6 +398,12 @@ const SciWorkaroundEntry kAbs_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kArraySetElements_workarounds[] = { + { 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 +}; + +// 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 @@ -544,6 +552,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 @@ -670,6 +684,20 @@ 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 + 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 @@ -699,7 +727,7 @@ const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = { // 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,0, 0, -1, "MG", "setCursor", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // At the start of the game, an object is passed as the cel number + { 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 }; @@ -767,17 +795,12 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround -const SciWorkaroundEntry kStringPutAt_workarounds[] = { - { GID_PHANTASMAGORIA,902, 64918, 0, "Str", "callKernel", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // When starting a new game from after chapter 1, the game tries to save ego's object in a string -}; - -// 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, SciTrackOriginReply *trackOrigin) { +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"); @@ -789,37 +812,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--; - const 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 @@ -895,12 +895,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 2cccd05475..d1e985dcf1 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; @@ -76,6 +70,7 @@ 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[]; @@ -90,20 +85,22 @@ 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[]; 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 kStringPutAt_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 b267d2ebc2..44ac7ef4cf 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -109,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() { } @@ -138,8 +142,8 @@ static int altify(int ch) { SciEvent EventManager::getScummVMEvent() { #ifdef ENABLE_SCI32 - SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; - SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; + 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() }; @@ -169,17 +173,20 @@ SciEvent EventManager::getScummVMEvent() { if (getSciVersion() >= SCI_VERSION_2) { const Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer(); + Common::Point mousePosSci = mousePos; + mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight)); + noEvent.mousePosSci = input.mousePosSci = mousePosSci; + if (ev.type == Common::EVENT_MOUSEMOVE) { // 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); + if (_hotRectanglesActive) { + checkHotRectangles(mousePosSci); + } } - Common::Point mousePosSci = mousePos; - mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight)); - noEvent.mousePosSci = input.mousePosSci = mousePosSci; - } else { #endif g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x); @@ -198,7 +205,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; } @@ -365,12 +372,14 @@ void EventManager::updateScreen() { SciEvent EventManager::getSciEvent(uint32 mask) { #ifdef ENABLE_SCI32 - SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; + 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 { @@ -401,24 +410,43 @@ SciEvent EventManager::getSciEvent(uint32 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 15a94b3e73..23b59f964a 100644 --- a/engines/sci/event.h +++ b/engines/sci/event.h @@ -50,6 +50,8 @@ struct SciEvent { * in script coordinates. */ Common::Point mousePosSci; + + int16 hotRectangleIndex; #endif }; @@ -60,6 +62,9 @@ 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) @@ -138,6 +143,17 @@ private: 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/celobj32.cpp b/engines/sci/graphics/celobj32.cpp index d67a4dc03c..430500ce1e 100644 --- a/engines/sci/graphics/celobj32.cpp +++ b/engines/sci/graphics/celobj32.cpp @@ -28,6 +28,7 @@ #include "sci/graphics/palette32.h" #include "sci/graphics/remap32.h" #include "sci/graphics/text32.h" +#include "sci/engine/workarounds.h" namespace Sci { #pragma mark CelScaler @@ -35,9 +36,6 @@ namespace Sci { 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; @@ -50,14 +48,12 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { 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; } } @@ -159,17 +155,19 @@ struct SCALER_NoScale { template<bool FLIP, typename READER> struct SCALER_Scale { #ifndef NDEBUG + int16 _minX; int16 _maxX; #endif const byte *_row; READER _reader; int16 _x; - static int16 _valuesX[4096]; - static int16 _valuesY[4096]; + 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 @@ -221,17 +219,17 @@ struct SCALER_Scale { } else { if (FLIP) { const int lastIndex = celObj._width - 1; - for (int16 x = 0; x < targetRect.width(); ++x) { - _valuesX[targetRect.left + x] = lastIndex - table->valuesX[x]; + for (int16 x = targetRect.left; x < targetRect.right; ++x) { + _valuesX[x] = lastIndex - table->valuesX[x - scaledPosition.x]; } } else { - for (int16 x = 0; x < targetRect.width(); ++x) { - _valuesX[targetRect.left + x] = table->valuesX[x]; + for (int16 x = targetRect.left; x < targetRect.right; ++x) { + _valuesX[x] = table->valuesX[x - scaledPosition.x]; } } - for (int16 y = 0; y < targetRect.height(); ++y) { - _valuesY[targetRect.top + y] = table->valuesY[y]; + for (int16 y = targetRect.top; y < targetRect.bottom; ++y) { + _valuesY[y] = table->valuesY[y - scaledPosition.y]; } } } @@ -239,19 +237,19 @@ struct SCALER_Scale { inline void setTarget(const int16 x, const int16 y) { _row = _reader.getRow(_valuesY[y]); _x = x; - assert(_x >= 0 && _x <= _maxX); + assert(_x >= _minX && _x <= _maxX); } inline byte read() { - assert(_x >= 0 && _x <= _maxX); + assert(_x >= _minX && _x <= _maxX); return _row[_valuesX[_x++]]; } }; template<bool FLIP, typename READER> -int16 SCALER_Scale<FLIP, READER>::_valuesX[4096]; +int16 SCALER_Scale<FLIP, READER>::_valuesX[kCelScalerTableSize]; template<bool FLIP, typename READER> -int16 SCALER_Scale<FLIP, READER>::_valuesY[4096]; +int16 SCALER_Scale<FLIP, READER>::_valuesY[kCelScalerTableSize]; #pragma mark - #pragma mark CelObj - Resource readers @@ -283,13 +281,13 @@ public: struct READER_Compressed { private: const byte *const _resource; - byte _buffer[4096]; + byte _buffer[kCelScalerTableSize]; uint32 _controlOffset; uint32 _dataOffset; uint32 _uncompressedDataOffset; int16 _y; const int16 _sourceHeight; - const uint8 _transparentColor; + const uint8 _skipColor; const int16 _maxWidth; public: @@ -297,7 +295,7 @@ public: _resource(celObj.getResPointer()), _y(-1), _sourceHeight(celObj._height), - _transparentColor(celObj._transparentColor), + _skipColor(celObj._skipColor), _maxWidth(maxWidth) { assert(maxWidth <= celObj._width); @@ -328,7 +326,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); @@ -570,7 +568,7 @@ uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const { void CelObj::submitPalette() const { if (_hunkPaletteOffset) { - HunkPalette palette(getResPointer() + _hunkPaletteOffset); + const HunkPalette palette(getResPointer() + _hunkPaletteOffset); g_sci->_gfxPalette32->submit(palette); } } @@ -667,7 +665,7 @@ void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common MAPPER mapper; SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaledPosition); - RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _transparentColor); + RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _skipColor); renderer.draw(target, targetRect, scaledPosition); } @@ -677,10 +675,10 @@ void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common MAPPER mapper; SCALER scaler(*this, targetRect, scaledPosition, scaleX, scaleY); if (_drawBlackLines) { - RENDERER<MAPPER, SCALER, true> renderer(mapper, scaler, _transparentColor); + RENDERER<MAPPER, SCALER, true> renderer(mapper, scaler, _skipColor); renderer.draw(target, targetRect, scaledPosition); } else { - RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _transparentColor); + RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _skipColor); renderer.draw(target, targetRect, scaledPosition); } } @@ -812,7 +810,31 @@ int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) { const byte *const data = resource->data; const uint16 loopCount = data[2]; - if (loopNo >= loopCount || loopNo < 0) { + + // 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 + 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; + } + } + + if (loopNo > loopCount || loopNo < 0) { return 0; } @@ -869,20 +891,20 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int 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 = kLowResX; - _scaledHeight = kLowResY; + _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; } } @@ -936,9 +958,9 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int _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); + _origin.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1; + _skipColor = celHeader[8]; _compressionType = (CelCompressionType)celHeader[9]; if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) { @@ -967,7 +989,7 @@ bool CelObjView::analyzeUncompressedForRemap() const { if ( pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && - pixel != _transparentColor + pixel != _skipColor ) { return true; } @@ -984,7 +1006,7 @@ bool CelObjView::analyzeForRemap() const { if ( pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && - pixel != _transparentColor + pixel != _skipColor ) { return true; } @@ -1058,9 +1080,9 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _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); @@ -1070,17 +1092,17 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { const uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12); if (sizeFlag2) { - _scaledWidth = sizeFlag1; - _scaledHeight = sizeFlag2; + _xResolution = sizeFlag1; + _yResolution = sizeFlag2; } else if (sizeFlag1 == 0) { - _scaledWidth = kLowResX; - _scaledHeight = kLowResY; + _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) { @@ -1105,7 +1127,7 @@ bool CelObjPic::analyzeUncompressedForSkip() const { 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; } } @@ -1151,10 +1173,10 @@ CelObjMem::CelObjMem(const reg_t bitmapObject) { _width = bitmap->getWidth(); _height = bitmap->getHeight(); - _displace = bitmap->getDisplace(); - _transparentColor = bitmap->getSkipColor(); - _scaledWidth = bitmap->getScaledWidth(); - _scaledHeight = bitmap->getScaledHeight(); + _origin = bitmap->getOrigin(); + _skipColor = bitmap->getSkipColor(); + _xResolution = bitmap->getXResolution(); + _yResolution = bitmap->getYResolution(); _hunkPaletteOffset = bitmap->getHunkPaletteOffset(); _remap = bitmap->getRemap(); } @@ -1173,10 +1195,10 @@ byte *CelObjMem::getResPointer() const { 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; diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h index 21e86d03e0..03d950a3c3 100644 --- a/engines/sci/graphics/celobj32.h +++ b/engines/sci/graphics/celobj32.h @@ -141,13 +141,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[4096]; + int valuesX[kCelScalerTableSize]; /** * The ratio used to generate the x-values. @@ -159,7 +166,7 @@ struct CelScalerTable { * the correct row to read from a source bitmap when * drawing a scaled version of the source bitmap. */ - int valuesY[4096]; + int valuesY[kCelScalerTableSize]; /** * The ratio used to generate the y-values. @@ -279,7 +286,7 @@ public: /** * TODO: Documentation */ - Common::Point _displace; + Common::Point _origin; /** * The dimensions of the original coordinate system for @@ -294,21 +301,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. diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index 7689655d1d..0cd924b38d 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -160,7 +160,6 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) { // Original engine did not have a QUIT event but we have to handle it if (event.type == SCI_EVENT_QUIT) { focused = false; - break; } else if (event.type == SCI_EVENT_MOUSE_PRESS && !editorPlaneRect.contains(event.mousePosSci)) { focused = false; } else if (event.type == SCI_EVENT_KEYBOARD) { @@ -313,8 +312,8 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) { if (textChanged) { editor.text.trim(); - SciString *string = _segMan->lookupString(textObject); - string->fromString(editor.text); + SciArray &string = *_segMan->lookupArray(textObject); + string.fromString(editor.text); } return make_reg(0, textChanged); @@ -400,7 +399,7 @@ ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, con const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; Common::Rect bitmapRect(gameRect); - mulinc(bitmapRect, Ratio(_gfxText32._scaledWidth, scriptWidth), Ratio(_gfxText32._scaledHeight, scriptHeight)); + mulinc(bitmapRect, Ratio(_gfxText32._xResolution, scriptWidth), Ratio(_gfxText32._yResolution, scriptHeight)); _textRect.left = 2; _textRect.top = 2; @@ -441,6 +440,11 @@ void ScrollWindow::show() { } 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; diff --git a/engines/sci/graphics/cursor32.cpp b/engines/sci/graphics/cursor32.cpp index 88150db6e6..2f2611c769 100644 --- a/engines/sci/graphics/cursor32.cpp +++ b/engines/sci/graphics/cursor32.cpp @@ -122,6 +122,7 @@ void GfxCursor32::drawToHardware(const DrawRegion &source) { 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() { @@ -181,7 +182,7 @@ void GfxCursor32::setView(const GuiResourceId viewId, const int16 loopNo, const if (_macCursorRemap.empty() && viewId != -1) { CelObjView view(viewId, loopNo, celNo); - _hotSpot = view._displace; + _hotSpot = view._origin; _width = view._width; _height = view._height; diff --git a/engines/sci/graphics/cursor32.h b/engines/sci/graphics/cursor32.h index 88a75beb7f..00a8b9baa4 100644 --- a/engines/sci/graphics/cursor32.h +++ b/engines/sci/graphics/cursor32.h @@ -31,7 +31,7 @@ namespace Sci { -class GfxCursor32 : Common::Serializable { +class GfxCursor32 : public Common::Serializable { public: GfxCursor32(); ~GfxCursor32(); 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 4e0aa22669..843fe5ed86 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -58,7 +58,7 @@ namespace Sci { GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor) : - _isHiRes(ConfMan.getBool("enable_high_resolution_graphics")), + _isHiRes(gameIsHiRes()), _palette(palette), _cursor(cursor), _segMan(segMan), @@ -71,11 +71,6 @@ GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitio _overdrawThreshold(0), _palMorphIsOn(false) { - // QFG4 is the only SCI32 game that doesn't have a high-resolution version - if (g_sci->getGameId() == GID_QFG4) { - _isHiRes = false; - } - if (g_sci->getGameId() == GID_PHANTASMAGORIA) { _currentBuffer = Buffer(630, 450, nullptr); } else if (_isHiRes) { @@ -146,7 +141,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); @@ -213,6 +208,26 @@ 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 @@ -550,7 +565,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *show Palette sourcePalette(_palette->getNextPalette()); alterVmap(sourcePalette, sourcePalette, -1, styleRanges); - int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); + int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][kGlobalVarPreviousRoomNo].toSint16(); Common::Rect rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight); _showList.add(rect); @@ -989,6 +1004,10 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL _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; } @@ -1108,6 +1127,7 @@ void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showLi void GfxFrameout::showBits() { if (!_showList.size()) { + g_system->updateScreen(); return; } @@ -1146,6 +1166,7 @@ void GfxFrameout::showBits() { _cursor->donePainting(); _showList.clear(); + g_system->updateScreen(); } void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) { @@ -1327,7 +1348,7 @@ bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const scaledPosition.x -= screenItem._scaledPosition.x; scaledPosition.y -= screenItem._scaledPosition.y; - mulru(scaledPosition, Ratio(celObj._scaledWidth, _currentBuffer.screenWidth), Ratio(celObj._scaledHeight, _currentBuffer.screenHeight)); + mulru(scaledPosition, Ratio(celObj._xResolution, _currentBuffer.screenWidth), Ratio(celObj._yResolution, _currentBuffer.screenHeight)); if (screenItem._scale.signal != kScaleSignalNone && screenItem._scale.x && screenItem._scale.y) { scaledPosition.x = scaledPosition.x * 128 / screenItem._scale.x; @@ -1335,7 +1356,7 @@ bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const } uint8 pixel = celObj.readPixel(scaledPosition.x, scaledPosition.y, mirrorX); - return pixel != celObj._transparentColor; + return pixel != celObj._skipColor; } return true; diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index e4caffd9e5..9481b0ef5a 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -44,6 +44,12 @@ private: GfxPalette32 *_palette; SegManager *_segMan; + /** + * Determines whether the current game should be rendered in + * high resolution. + */ + bool gameIsHiRes() const; + public: GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor); ~GfxFrameout(); diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp index 91817d4060..aac922d278 100644 --- a/engines/sci/graphics/paint16.cpp +++ b/engines/sci/graphics/paint16.cpp @@ -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/paint32.cpp b/engines/sci/graphics/paint32.cpp index 338b70901e..bf7c73623e 100644 --- a/engines/sci/graphics/paint32.cpp +++ b/engines/sci/graphics/paint32.cpp @@ -38,7 +38,6 @@ reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &st Common::Rect gameRect; reg_t bitmapId = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect); - SciBitmap &bitmap = *_segMan->lookupBitmap(bitmapId); CelInfo32 celInfo; celInfo.type = kCelTypeMem; @@ -48,7 +47,7 @@ reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &st // `kUpdateLine` can get the originally used color celInfo.color = color; - ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, Common::Rect(startPoint.x, startPoint.y, startPoint.x + bitmap.getWidth(), startPoint.y + bitmap.getHeight())); + ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, gameRect); screenItem->_priority = priority; screenItem->_fixedPriority = true; @@ -89,9 +88,13 @@ void GfxPaint32::plotter(int x, int y, int color, void *data) { LineProperties &properties = *static_cast<LineProperties *>(data); byte *pixels = properties.bitmap->getPixels(); - const uint32 index = properties.bitmap->getWidth() * y + x; + const uint16 bitmapWidth = properties.bitmap->getWidth(); + const uint16 bitmapHeight = properties.bitmap->getHeight(); + const uint32 index = bitmapWidth * y + x; - if (index < properties.bitmap->getDataSize()) { + // 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; @@ -112,25 +115,29 @@ void GfxPaint32::plotter(int x, int y, int color, void *data) { if (properties.patternIndex == ARRAYSIZE(properties.pattern)) { properties.patternIndex = 0; } - } else { - warning("GfxPaint32::plotter: Attempted to write out of bounds (%u >= %u)", index, properties.bitmap->getDataSize()); } } 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; - // Thickness is expected to be 2n+1 - thickness = ((MAX((uint8)1, thickness) - 1) | 1); + // Line thickness is expected to be 2 * thickness + 1 + thickness = (MAX<uint8>(1, thickness) - 1) | 1; const uint8 halfThickness = thickness >> 1; - outRect.left = (startPoint.x < endPoint.x ? startPoint.x : endPoint.x) - halfThickness; - outRect.top = (startPoint.y < endPoint.y ? startPoint.y : endPoint.y) - halfThickness; - outRect.right = (startPoint.x > endPoint.x ? startPoint.x : endPoint.x) + halfThickness + 1; - outRect.bottom = (startPoint.y > endPoint.y ? startPoint.y : endPoint.y) + halfThickness + 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 + + outRect.grow(halfThickness); + outRect.clip(Common::Rect(scriptWidth, scriptHeight)); reg_t bitmapId; - SciBitmap &bitmap = *_segMan->allocateBitmap(&bitmapId, outRect.width(), outRect.height(), skipColor, 0, 0, g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, 0, false, true); + 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()); @@ -152,12 +159,11 @@ reg_t GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common:: break; } - const Common::Rect drawRect( - startPoint.x - outRect.left, - startPoint.y - outRect.top, - endPoint.x - outRect.left, - endPoint.y - outRect.top - ); + // 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) { @@ -166,14 +172,14 @@ reg_t GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common:: } properties.patternIndex = 0; - properties.horizontal = ABS(drawRect.right - drawRect.left) > ABS(drawRect.bottom - drawRect.top); - properties.lastAddress = properties.horizontal ? drawRect.left : drawRect.top; + properties.horizontal = ABS(x2 - x1) > ABS(y2 - y1); + properties.lastAddress = properties.horizontal ? x1 : y1; } if (thickness <= 1) { - Graphics::drawLine(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, color, plotter, &properties); + Graphics::drawLine(x1, y1, x2, y2, color, plotter, &properties); } else { - Graphics::drawThickLine2(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, thickness, color, plotter, &properties); + Graphics::drawThickLine2(x1, y1, x2, y2, thickness, color, plotter, &properties); } return bitmapId; diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp index c7098bc3e4..ec3d912365 100644 --- a/engines/sci/graphics/palette32.cpp +++ b/engines/sci/graphics/palette32.cpp @@ -52,7 +52,7 @@ HunkPalette::HunkPalette(byte *rawPalette) : } } -void HunkPalette::setVersion(const uint32 version) { +void HunkPalette::setVersion(const uint32 version) const { if (_numPalettes != _data[10]) { error("Invalid HunkPalette"); } @@ -93,9 +93,9 @@ const Palette HunkPalette::toPalette() const { if (_numPalettes) { const EntryHeader header = getEntryHeader(); - byte *data = getPalPointer() + kEntryHeaderSize; + const byte *data = getPalPointer() + kEntryHeaderSize; - int16 end = header.startColor + header.numColors; + const int16 end = header.startColor + header.numColors; assert(end <= 256); if (header.sharedUsed) { @@ -118,20 +118,19 @@ const Palette HunkPalette::toPalette() const { return outPalette; } - #pragma mark - #pragma mark GfxPalette32 GfxPalette32::GfxPalette32(ResourceManager *resMan) : _resMan(resMan), + // Palette versioning _version(1), _needsUpdate(false), _currentPalette(), _sourcePalette(), _nextPalette(), - // Clut - _clutTable(nullptr), + // Palette varying _varyStartPalette(nullptr), _varyTargetPalette(nullptr), @@ -142,6 +141,7 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan) _varyDirection(0), _varyTargetPercent(0), _varyNumTimesPaused(0), + // Palette cycling _cyclers(), _cycleMap() { @@ -161,44 +161,6 @@ GfxPalette32::~GfxPalette32() { cycleAllOff(); } -inline void mergePaletteInternal(Palette *const to, const Palette *const from) { - // The last color is always white, so it is not copied. - // (Some palettes try to set the last color, which causes - // churning in the palettes when they are merged) - for (int i = 0, len = ARRAYSIZE(to->colors) - 1; i < len; ++i) { - if (from->colors[i].used) { - to->colors[i] = from->colors[i]; - } - } -} - -void GfxPalette32::submit(const Palette &palette) { - const Palette oldSourcePalette(_sourcePalette); - mergePaletteInternal(&_sourcePalette, &palette); - - if (!_needsUpdate && _sourcePalette != oldSourcePalette) { - ++_version; - _needsUpdate = true; - } -} - -void GfxPalette32::submit(HunkPalette &hunkPalette) { - if (hunkPalette.getVersion() == _version) { - return; - } - - const Palette oldSourcePalette(_sourcePalette); - const Palette palette = hunkPalette.toPalette(); - mergePaletteInternal(&_sourcePalette, &palette); - - if (!_needsUpdate && oldSourcePalette != _sourcePalette) { - ++_version; - _needsUpdate = true; - } - - hunkPalette.setVersion(_version); -} - bool GfxPalette32::loadPalette(const GuiResourceId resourceId) { Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); @@ -206,7 +168,7 @@ bool GfxPalette32::loadPalette(const GuiResourceId resourceId) { return false; } - HunkPalette palette(palResource->data); + const HunkPalette palette(palResource->data); submit(palette); return true; } @@ -241,6 +203,33 @@ int16 GfxPalette32::matchColor(const uint8 r, const uint8 g, const uint8 b) { return bestIndex; } +void GfxPalette32::submit(const Palette &palette) { + const Palette oldSourcePalette(_sourcePalette); + mergePalette(_sourcePalette, palette); + + if (!_needsUpdate && _sourcePalette != oldSourcePalette) { + ++_version; + _needsUpdate = true; + } +} + +void GfxPalette32::submit(const HunkPalette &hunkPalette) { + if (hunkPalette.getVersion() == _version) { + return; + } + + 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(); _needsUpdate = false; @@ -266,10 +255,10 @@ void GfxPalette32::updateHardware(const bool updateScreen) { _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 + // 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. @@ -295,7 +284,30 @@ void GfxPalette32::updateHardware(const bool updateScreen) { g_system->getPaletteManager()->setPalette(bpal, 0, 256); if (updateScreen) { - g_sci->getEventManager()->updateScreen(); + g_system->updateScreen(); + } +} + +Palette GfxPalette32::getPaletteFromResource(const GuiResourceId resourceId) const { + Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); + + if (!palResource) { + error("Could not load vary palette %d", resourceId); + } + + const HunkPalette rawPalette(palResource->data); + return rawPalette.toPalette(); +} + +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]; + } } } @@ -306,81 +318,43 @@ void GfxPalette32::applyAll() { } #pragma mark - -#pragma mark Colour look-up - -#ifdef ENABLE_SCI3_GAMES -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); +#pragma mark Varying - Palette pal; - memset(&pal, 0, sizeof(Palette)); +void GfxPalette32::setVary(const Palette &target, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor) { + setTarget(target); + setVaryTime(percent, ticks); - // Setup 1:1 mapping - for (int i = 0; i < 256; i++) { - pal.mapping[i] = i; + if (fromColor > -1) { + _varyFromColor = fromColor; } - - // 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 (toColor > -1) { + assert(toColor < 256); + _varyToColor = toColor; } - - set(&pal, true); - setOnScreen(); - return true; } -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::setVaryPercent(const int16 percent, const int32 ticks) { + if (_varyTargetPalette != nullptr) { + setVaryTime(percent, ticks); + } -void GfxPalette32::unloadClut() { - // This will only unload the actual table, but not reset any palette - delete[] _clutTable; - _clutTable = nullptr; + // NOTE: SSCI had two additional parameters for this function to change the + // `_varyFromColor`, but they were always hardcoded to be ignored } -#endif - -#pragma mark - -#pragma mark Varying - -inline Palette GfxPalette32::getPaletteFromResourceInternal(const GuiResourceId resourceId) const { - Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); - if (!palResource) { - error("Could not load vary palette %d", resourceId); +void GfxPalette32::setVaryTime(const int32 time) { + if (_varyTargetPalette != nullptr) { + setVaryTime(_varyTargetPercent, time); } - - HunkPalette rawPalette(palResource->data); - return rawPalette.toPalette(); } -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) { @@ -395,72 +369,6 @@ inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int tim } } -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; @@ -479,14 +387,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; @@ -497,51 +397,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) { @@ -552,7 +445,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 { @@ -560,7 +453,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; @@ -591,82 +484,67 @@ 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) { 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 uint32 updateDelta = now - candidate->lastUpdateTick; if (updateDelta < minUpdateDelta) { @@ -678,11 +556,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 (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) { + 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; @@ -691,30 +572,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; } @@ -724,8 +605,8 @@ void GfxPalette32::cycleAllOn() { void GfxPalette32::cycleAllPause() { // NOTE: The original engine did not check for null pointers in the // palette cyclers pointer array. - 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) { // This seems odd, because currentCycle is 0..numColorsPerCycle, // but fromColor is 0..255. When applyAllCycles runs, the values @@ -736,8 +617,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; } @@ -745,8 +626,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; @@ -757,8 +638,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; @@ -767,16 +648,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]; } @@ -788,15 +710,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; } } @@ -810,31 +733,31 @@ 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(uint16 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]; diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h index dc2158022f..81e9bbbfd3 100644 --- a/engines/sci/graphics/palette32.h +++ b/engines/sci/graphics/palette32.h @@ -24,30 +24,33 @@ #define SCI_GRAPHICS_PALETTE32_H #include "sci/graphics/palette.h" - namespace Sci { +#pragma mark HunkPalette + /** - * HunkPalette represents a raw palette resource - * read from disk. + * 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. + * 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); + void setVersion(const uint32 version) const; /** - * Converts the hunk palette to a standard - * palette. + * Converts the hunk palette to a standard Palette. */ const Palette toPalette() const; @@ -64,15 +67,14 @@ private: kEntryHeaderSize = 22, /** - * The offset of the hunk palette version - * within the palette entry header. + * The offset of the hunk palette version within the palette entry + * header. */ kEntryVersionOffset = 18 }; /** - * The header for a palette inside the - * HunkPalette. + * The header for a palette inside the HunkPalette. */ struct EntryHeader { /** @@ -81,8 +83,7 @@ private: uint8 startColor; /** - * The number of palette colors in this - * entry. + * The number of palette colors in this entry. */ uint16 numColors; @@ -92,8 +93,7 @@ private: bool used; /** - * Whether or not all palette entries - * share the same `used` value in + * Whether or not all palette entries share the same `used` value in * `defaultFlag`. */ bool sharedUsed; @@ -105,14 +105,14 @@ private: }; /** - * The version number from the last time this - * palette was submitted to GfxPalette32. + * The version number from the last time this palette was submitted to + * GfxPalette32. */ - uint32 _version; + mutable uint32 _version; /** - * The number of palettes stored in the hunk - * palette. In SCI32 games this is always 1. + * The number of palettes stored in the hunk palette. In SCI32 games this is + * always 1. */ uint8 _numPalettes; @@ -122,41 +122,44 @@ private: 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. + * 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. + * 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; @@ -166,15 +169,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; @@ -184,17 +187,72 @@ struct PalCycler { uint16 numTimesPaused; }; +#pragma mark - +#pragma mark GfxPalette32 + class GfxPalette32 { public: 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: ResourceManager *_resMan; /** - * The palette revision version. Increments once per game - * loop that changes the source palette. + * The palette revision version. Increments once per game loop that changes + * the source palette. */ uint32 _version; @@ -209,90 +267,140 @@ private: 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 _currentPalette. + * 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: - void saveLoadWithSerializer(Common::Serializer &s); - inline const Palette &getNextPalette() const { return _nextPalette; }; - inline const Palette &getCurrentPalette() const { return _currentPalette; }; + /** + * 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); /** - * Loads a palette into GfxPalette32 with the given resource - * ID. + * Gets the current vary blend amount. */ - bool loadPalette(const GuiResourceId resourceId); + inline int16 getVaryPercent() const { return ABS(_varyPercent); } /** - * Finds the nearest color in the current palette matching the - * given RGB value. + * Changes the percentage of the current vary to `percent`, to be completed + * over `time` ticks, if there is a currently active vary target palette. */ - int16 matchColor(const uint8 r, const uint8 g, const uint8 b); + void setVaryPercent(const int16 percent, const int32 time); /** - * Submits a palette to display. Entries marked as “used” in the - * submitted palette are merged into the existing entries of - * _sourcePalette. + * Changes the amount of time, in ticks, an in-progress palette vary should + * take to finish. */ - void submit(const Palette &palette); - void submit(HunkPalette &palette); + void setVaryTime(const int32 ticks); - bool updateForFrame(); - void updateFFrame(); - void updateHardware(const bool updateScreen = true); - void applyAll(); + /** + * Changes the vary percent and time to perform the vary. + */ + void setVaryTime(const int16 percent, 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. + * Removes the active palette vary. */ - byte *_clutTable; + void varyOff(); -public: - bool loadClut(uint16 clutId); - byte matchClutColor(uint16 color); - void unloadClut(); + /** + * 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; @@ -302,10 +410,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. @@ -313,97 +421,158 @@ 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); - void kernelPalVaryPause(bool pause); - - 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. * - * The cycle map is also by the color remapping system to - * avoid attempting to remap to palette entries that are - * cycling (so won't be the expected color once the cycler - * runs again). + * @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(); - inline const bool *getCycleMap() const { return _cycleMap; } #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. */ - uint16 _fadeTable[256]; + void setFade(const uint16 percent, const uint8 fromColor, const uint16 toColor); -public: /** - * Sets the intensity level for a range of palette - * entries. An intensity of zero indicates total - * darkness. Intensity may be set to over 100 percent. + * Resets the intensity of all palette entries to 100%. */ - void setFade(const uint16 percent, const uint8 fromColor, const uint16 toColor); 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 diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp index 0025b24476..75a885da57 100644 --- a/engines/sci/graphics/picture.cpp +++ b/engines/sci/graphics/picture.cpp @@ -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 @@ -1239,7 +1239,7 @@ void GfxPicture::vectorPatternTexturedCircle(Common::Rect box, byte size, byte c byte bitNo = 0; const bool *textureData = &vectorPatternTextures[vectorPatternTextureOffset[texture]]; int y, x; - + for (y = box.top; y < box.bottom; y++) { for (x = box.left; x < box.right; x++) { if (bitmap & 1) { diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp index 1cd88d667b..e7da8b815d 100644 --- a/engines/sci/graphics/plane32.cpp +++ b/engines/sci/graphics/plane32.cpp @@ -527,14 +527,8 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force if (item != nullptr) { // update item in visiblePlane if item is updated - if ( - item->_updated || - ( - forceUpdate && - visiblePlane != nullptr && - visiblePlane->_screenItemList.findByObject(item->_object) != nullptr - ) - ) { + if (visiblePlane != nullptr && ( + item->_updated || (forceUpdate && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr))) { *visiblePlane->_screenItemList[i] = *item; } diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index 601ab9f09f..23e92ef6a9 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 @@ -58,7 +58,7 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { // 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) { @@ -66,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 @@ -145,7 +145,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); @@ -314,7 +314,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 @@ -628,7 +628,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 f4ed269265..9b0d390cc3 100644 --- a/engines/sci/graphics/screen_item32.cpp +++ b/engines/sci/graphics/screen_item32.cpp @@ -263,33 +263,30 @@ void ScreenItem::calcRects(const Plane &plane) { } Ratio scaleX, scaleY; - - if (_scale.signal & kScaleSignalDoScaling32) { - if (_scale.signal & kScaleSignalUseVanishingPoint) { - int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y); - scaleX = Ratio(num, 128); - scaleY = Ratio(num, 128); - } else { - scaleX = Ratio(_scale.x, 128); - scaleY = Ratio(_scale.y, 128); - } + 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() && scaleY.getNumerator()) { _screenItemRect = _insetRect; - const Ratio celToScreenX(screenWidth, celObj._scaledWidth); - const Ratio celToScreenY(screenHeight, celObj._scaledHeight); + 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._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) { + if (celObj._xResolution != kLowResX || celObj._yResolution != kLowResY) { // high resolution coordinates if (_useInsetRect) { - const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth); - const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); + const Ratio scriptToCelX(celObj._xResolution, scriptWidth); + const Ratio scriptToCelY(celObj._yResolution, scriptHeight); mulru(_screenItemRect, scriptToCelX, scriptToCelY, 0); if (_screenItemRect.intersects(celRect)) { @@ -299,11 +296,11 @@ 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; + originX = celObj._width - celObj._origin.x - 1; } if (!scaleX.isOne() || !scaleY.isOne()) { @@ -331,13 +328,13 @@ void ScreenItem::calcRects(const Plane &plane) { } } - displaceX = (displaceX * scaleX).toInt(); - displaceY = (displaceY * scaleY).toInt(); + originX = (originX * scaleX).toInt(); + originY = (originY * scaleY).toInt(); } mulinc(_screenItemRect, celToScreenX, celToScreenY); - displaceX = (displaceX * celToScreenX).toInt(); - displaceY = (displaceY * celToScreenY).toInt(); + originX = (originX * celToScreenX).toInt(); + originY = (originY * celToScreenY).toInt(); const Ratio scriptToScreenX = Ratio(screenWidth, scriptWidth); const Ratio scriptToScreenY = Ratio(screenHeight, scriptHeight); @@ -346,8 +343,8 @@ void ScreenItem::calcRects(const Plane &plane) { _scaledPosition.x = _position.x; _scaledPosition.y = _position.y; } else { - _scaledPosition.x = (_position.x * scriptToScreenX).toInt() - displaceX; - _scaledPosition.y = (_position.y * scriptToScreenY).toInt() - displaceY; + _scaledPosition.x = (_position.x * scriptToScreenX).toInt() - originX; + _scaledPosition.y = (_position.y * scriptToScreenY).toInt() - originY; } _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y); @@ -365,7 +362,7 @@ void ScreenItem::calcRects(const Plane &plane) { if (celObjPic == nullptr) { error("Expected a CelObjPic"); } - temp.translate((celObjPic->_relativePosition.x * scriptToScreenX).toInt() - displaceX, 0); + temp.translate((celObjPic->_relativePosition.x * scriptToScreenX).toInt() - originX, 0); // TODO: This is weird. int deltaX = plane._planeRect.width() - temp.right - 1 - temp.left; @@ -383,9 +380,9 @@ void ScreenItem::calcRects(const Plane &plane) { } else { // low resolution coordinates - int displaceX = celObj._displace.x; + int originX = celObj._origin.x; if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) { - displaceX = celObj._width - celObj._displace.x - 1; + originX = celObj._width - celObj._origin.x - 1; } if (!scaleX.isOne() || !scaleY.isOne()) { @@ -397,8 +394,8 @@ void ScreenItem::calcRects(const Plane &plane) { _screenItemRect.bottom -= 1; } - _scaledPosition.x = _position.x - (displaceX * scaleX).toInt(); - _scaledPosition.y = _position.y - (celObj._displace.y * scaleY).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) { @@ -413,7 +410,7 @@ void ScreenItem::calcRects(const Plane &plane) { if (celObjPic == nullptr) { error("Expected a CelObjPic"); } - temp.translate(celObjPic->_relativePosition.x - (displaceX * scaleX).toInt(), celObjPic->_relativePosition.y - (celObj._displace.y * scaleY).toInt()); + temp.translate(celObjPic->_relativePosition.x - (originX * scaleX).toInt(), celObjPic->_relativePosition.y - (celObj._origin.y * scaleY).toInt()); // TODO: This is weird. int deltaX = plane._gameRect.width() - temp.right - 1 - temp.left; @@ -426,7 +423,7 @@ void ScreenItem::calcRects(const Plane &plane) { _scaledPosition.y += plane._gameRect.top; _screenItemRect.translate(plane._gameRect.left, plane._gameRect.top); - if (celObj._scaledWidth != screenWidth || celObj._scaledHeight != screenHeight) { + if (celObj._xResolution != screenWidth || celObj._yResolution != screenHeight) { mulru(_scaledPosition, celToScreenX, celToScreenY); mulru(_screenItemRect, celToScreenX, celToScreenY, 1); } @@ -520,11 +517,11 @@ void ScreenItem::printDebugInfo(Console *con) const { _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 ); } } @@ -593,34 +590,32 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; Ratio scaleX, scaleY; - if (_scale.signal & kScaleSignalDoScaling32) { - if (_scale.signal & kScaleSignalUseVanishingPoint) { - int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y); - scaleX = Ratio(num, 128); - scaleY = Ratio(num, 128); - } else { - scaleX = Ratio(_scale.x, 128); - scaleY = Ratio(_scale.y, 128); - } + 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 displaceX = celObj._displace.x; - int16 displaceY = celObj._displace.y; + int16 originX = celObj._origin.x; + int16 originY = celObj._origin.y; if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) { - displaceX = celObj._width - displaceX - 1; + originX = celObj._width - originX - 1; } - if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) { + if (celObj._xResolution != kLowResX || celObj._yResolution != kLowResY) { // high resolution coordinates if (_useInsetRect) { - Ratio scriptToCelX(celObj._scaledWidth, scriptWidth); - Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); + Ratio scriptToCelX(celObj._xResolution, scriptWidth); + Ratio scriptToCelY(celObj._yResolution, scriptHeight); mulru(nsRect, scriptToCelX, scriptToCelY, 0); if (nsRect.intersects(celObjRect)) { @@ -661,14 +656,14 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { } } - Ratio celToScriptX(scriptWidth, celObj._scaledWidth); - Ratio celToScriptY(scriptHeight, celObj._scaledHeight); + Ratio celToScriptX(scriptWidth, celObj._xResolution); + Ratio celToScriptY(scriptHeight, celObj._yResolution); - displaceX = (displaceX * scaleX * celToScriptX).toInt(); - displaceY = (displaceY * scaleY * celToScriptY).toInt(); + originX = (originX * scaleX * celToScriptX).toInt(); + originY = (originY * scaleY * celToScriptY).toInt(); mulinc(nsRect, celToScriptX, celToScriptY); - nsRect.translate(_position.x - displaceX, _position.y - displaceY); + nsRect.translate(_position.x - originX, _position.y - originY); } else { // low resolution coordinates @@ -681,9 +676,9 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { nsRect.bottom -= 1; } - displaceX = (displaceX * scaleX).toInt(); - displaceY = (displaceY * scaleY).toInt(); - nsRect.translate(_position.x - displaceX, _position.y - displaceY); + 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); diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h index 4221c0ea52..009b608f18 100644 --- a/engines/sci/graphics/screen_item32.h +++ b/engines/sci/graphics/screen_item32.h @@ -30,13 +30,9 @@ namespace Sci { enum ScaleSignals32 { - kScaleSignalNone = 0, - // TODO: rename to 'manual' - 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 { diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp index cb6e614657..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; 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 11572581ff..d142ff75c3 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -39,8 +39,8 @@ namespace Sci { int16 GfxText32::_defaultFontId = 0; -int16 GfxText32::_scaledWidth = 0; -int16 GfxText32::_scaledHeight = 0; +int16 GfxText32::_xResolution = 0; +int16 GfxText32::_yResolution = 0; GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) : _segMan(segMan), @@ -52,10 +52,10 @@ GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) : _fontId = _defaultFontId; _font = _cache->getFont(_defaultFontId); - if (_scaledWidth == 0) { + if (_xResolution == 0) { // initialize the statics - _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; - _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; } } @@ -78,8 +78,8 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect 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(); @@ -96,7 +96,7 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect _textRect = Common::Rect(); } - _segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false, gc); + _segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _xResolution, _yResolution, 0, false, gc); erase(bitmapRect, false); @@ -120,12 +120,12 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight)); + mulinc(_textRect, Ratio(_xResolution, scriptWidth), Ratio(_yResolution, scriptHeight)); CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo); - _skipColor = view._transparentColor; - _width = view._width * _scaledWidth / view._scaledWidth; - _height = view._height * _scaledHeight / view._scaledHeight; + _skipColor = view._skipColor; + _width = view._width * _xResolution / view._xResolution; + _height = view._height * _yResolution / view._yResolution; Common::Rect bitmapRect(_width, _height); if (_textRect.intersects(bitmapRect)) { @@ -134,7 +134,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & _textRect = Common::Rect(); } - SciBitmap &bitmap = *_segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false, gc); + 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 @@ -144,7 +144,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & erase(bitmapRect, false); _backColor = backColor; - view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight)); + 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); @@ -183,13 +183,15 @@ void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint // NOTE: Not fully disassembled, but this should be right int16 rectWidth = targetRect.width(); - int16 sidesHeight = targetRect.height() - size * 2; + 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) { + 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) { @@ -201,9 +203,10 @@ void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint } pixels += stride; } - for (int16 y = 0; y < size; ++y) { + for (int16 y = 0; y < size && y < heightRemaining; ++y) { memset(pixels, color, rectWidth); pixels += _width; + --heightRemaining; } } @@ -329,7 +332,7 @@ void GfxText32::drawText(const uint index, uint length) { 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 * _scaledWidth / g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + bitmapStride = bitmapStride * _xResolution / g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; targetRect = scaleRect(rect); } @@ -554,13 +557,13 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - maxWidth = maxWidth * _scaledWidth / scriptWidth; + maxWidth = maxWidth * _xResolution / scriptWidth; _text = text; if (maxWidth >= 0) { if (maxWidth == 0) { - maxWidth = _scaledWidth * 3 / 5; + maxWidth = _xResolution * 3 / 5; } result.right = maxWidth; @@ -603,8 +606,8 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, 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 + _scaledWidth - 1) / _scaledWidth + 1; - result.bottom = ((result.bottom - 1) * scriptHeight + _scaledHeight - 1) / _scaledHeight + 1; + result.right = ((result.right - 1) * scriptWidth + _xResolution - 1) / _xResolution + 1; + result.bottom = ((result.bottom - 1) * scriptHeight + _yResolution - 1) / _yResolution + 1; } return result; @@ -627,7 +630,7 @@ int16 GfxText32::getTextCount(const Common::String &text, const uint index, cons Common::Rect scaledRect(textRect); if (doScaling) { - mulinc(scaledRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight)); + mulinc(scaledRect, Ratio(_xResolution, scriptWidth), Ratio(_yResolution, scriptHeight)); } Common::String oldText = _text; diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 44bd48afd5..c4521a4f56 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -151,8 +151,8 @@ private: 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); + Ratio scaleX(_xResolution, scriptWidth); + Ratio scaleY(_yResolution, scriptHeight); mulinc(scaledRect, scaleX, scaleY); return scaledRect; } @@ -169,13 +169,13 @@ public: * The size of the x-dimension of the coordinate system * used by the text renderer. Static since it was global in SSCI. */ - static int16 _scaledWidth; + 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 _scaledHeight; + static int16 _yResolution; /** * The currently active font resource used to write text @@ -199,12 +199,12 @@ public: inline int scaleUpWidth(int value) const { const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; - return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth; + return (value * scriptWidth + _xResolution - 1) / _xResolution; } inline int scaleUpHeight(int value) const { const int scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - return (value * scriptHeight + _scaledHeight - 1) / _scaledHeight; + return (value * scriptHeight + _yResolution - 1) / _yResolution; } /** diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp index 37f608da85..ee230f50a8 100644 --- a/engines/sci/graphics/transitions32.cpp +++ b/engines/sci/graphics/transitions32.cpp @@ -20,6 +20,7 @@ * */ +#include "sci/console.h" #include "sci/engine/segment.h" #include "sci/engine/seg_manager.h" #include "sci/engine/state.h" @@ -118,6 +119,7 @@ void GfxTransitions32::processShowStyles() { if (doFrameOut) { g_sci->_gfxFrameout->frameOut(true); + g_sci->getSciDebugger()->onFrame(); throttle(); } } while(continueProcessing && doFrameOut); @@ -233,92 +235,98 @@ void GfxTransitions32::kernelSetShowStyle(const uint16 argc, const reg_t planeOb } } - if (type > 0) { - 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<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(); - } + 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"); - } + // 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; + 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 (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 (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; - } + 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; } + + _showStyles.push_back(*entry); + delete entry; } } @@ -940,8 +948,9 @@ void GfxTransitions32::kernelSetScroll(const reg_t planeId, const int16 deltaX, g_sci->_gfxFrameout->frameOut(true); throttle(); } - delete scroll; } + + delete scroll; } bool GfxTransitions32::processScroll(PlaneScroll &scroll) { @@ -953,7 +962,7 @@ bool GfxTransitions32::processScroll(PlaneScroll &scroll) { int deltaX = scroll.deltaX; int deltaY = scroll.deltaY; - if (((scroll.x + deltaX) * scroll.y) <= 0) { + if (((scroll.x + deltaX) * scroll.x) <= 0) { deltaX = -scroll.x; } if (((scroll.y + deltaY) * scroll.y) <= 0) { @@ -965,6 +974,10 @@ bool GfxTransitions32::processScroll(PlaneScroll &scroll) { 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; diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp index 8b1d4ef32b..1db66644c8 100644 --- a/engines/sci/graphics/video32.cpp +++ b/engines/sci/graphics/video32.cpp @@ -58,7 +58,10 @@ SEQPlayer::SEQPlayer(SegManager *segMan) : void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) { delete _decoder; _decoder = new SEQDecoder(numTicks); - _decoder->loadFile(fileName); + 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 @@ -86,9 +89,8 @@ void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const _decoder->start(); while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { + g_sci->sleep(_decoder->getTimeToNextFrame()); renderFrame(); - g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); - g_sci->getEngineState()->_throttleTrigger = true; } _segMan->freeBitmap(_screenItem->_celInfo.bitmap); @@ -119,8 +121,8 @@ void SEQPlayer::renderFrame() const { } g_sci->_gfxFrameout->updateScreenItem(*_screenItem); - g_sci->getSciDebugger()->onFrame(); g_sci->_gfxFrameout->frameOut(true); + g_sci->getSciDebugger()->onFrame(); } #pragma mark - @@ -311,9 +313,8 @@ AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int1 void AVIPlayer::renderVideo() const { _decoder->start(); while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { - g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); - g_sci->getEngineState()->_throttleTrigger = true; - if (_decoder->needsUpdate()) { + g_sci->sleep(_decoder->getTimeToNextFrame()); + while (_decoder->needsUpdate()) { renderFrame(); } } @@ -408,8 +409,8 @@ void AVIPlayer::renderFrame() const { } g_sci->_gfxFrameout->updateScreenItem(*_screenItem); - g_sci->getSciDebugger()->onFrame(); g_sci->_gfxFrameout->frameOut(true); + g_sci->getSciDebugger()->onFrame(); } else { assert(surface->format.bytesPerPixel == 4); @@ -457,9 +458,8 @@ AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) { break; } - g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); - g_sci->getEngineState()->_throttleTrigger = true; - if (_decoder->needsUpdate()) { + g_sci->sleep(_decoder->getTimeToNextFrame()); + while (_decoder->needsUpdate()) { renderFrame(); } @@ -485,13 +485,6 @@ AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) { break; } } - - // TODO: Hot rectangles - if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) { - warning("Hot rectangles not implemented in VMD player"); - stopFlag = kEventFlagHotRectangle; - break; - } } return stopFlag; @@ -635,7 +628,7 @@ VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, co const int32 maxFrameNo = (int32)(_decoder->getFrameCount() - 1); if ((flags & kEventFlagToFrame) && lastFrameNo > 0) { - _decoder->setEndFrame(MIN((int32)lastFrameNo, maxFrameNo)); + _decoder->setEndFrame(MIN<int32>(lastFrameNo, maxFrameNo)); } else { _decoder->setEndFrame(maxFrameNo); } @@ -645,7 +638,7 @@ VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, co if (yieldInterval == -1 && !(flags & kEventFlagToFrame)) { _yieldInterval = lastFrameNo; } else if (yieldInterval != -1) { - _yieldInterval = MIN((int32)yieldInterval, maxFrameNo); + _yieldInterval = MIN<int32>(yieldInterval, maxFrameNo); } } else { _yieldInterval = maxFrameNo; @@ -655,11 +648,11 @@ VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, co } VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { - // Flushing all the keyboard and mouse events out of the event manager to - // avoid letting any events queued from before the video started from - // accidentally activating an event callback + // 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_QUIT); + 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) { @@ -667,8 +660,6 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { } } - _decoder->pauseVideo(false); - 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. @@ -698,12 +689,12 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { if (_doublePixels) { vmdScaleInfo.x = 256; vmdScaleInfo.y = 256; - vmdScaleInfo.signal = kScaleSignalDoScaling32; + vmdScaleInfo.signal = kScaleSignalManual; vmdRect.right += vmdRect.width(); vmdRect.bottom += vmdRect.height(); } else if (_stretchVertical) { vmdScaleInfo.y = 256; - vmdScaleInfo.signal = kScaleSignalDoScaling32; + vmdScaleInfo.signal = kScaleSignalManual; vmdRect.bottom += vmdRect.height(); } @@ -748,6 +739,14 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { g_sci->_gfxFrameout->addScreenItem(*_screenItem); + // HACK: When VMD playback is allowed to yield back to the VM, this + // causes the VMD decoder to freak out for some reason and video output + // starts jittering horribly. This problem does not happen if audio sync + // is disabled, but this causes some audible clicking between frames + // of audio (and, presumably, will cause some AV sync problems). Still, + // that's better than really bad jitter. + _decoder->setAudioSync(!(flags & kEventFlagYieldToVM)); + _decoder->start(); } @@ -758,9 +757,11 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { break; } - g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); - g_sci->getEngineState()->_throttleTrigger = true; - if (_decoder->needsUpdate()) { + // 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(); } @@ -802,15 +803,13 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { } } - // TODO: Hot rectangles - if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) { - warning("Hot rectangles not implemented in VMD player"); + event = _eventMan->getSciEvent(SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_PEEK); + if ((flags & kEventFlagHotRectangle) && event.type == SCI_EVENT_HOT_RECTANGLE) { stopFlag = kEventFlagHotRectangle; break; } } - _decoder->pauseVideo(true); return stopFlag; } @@ -857,9 +856,8 @@ void VMDPlayer::renderFrame() const { } } else { g_sci->_gfxFrameout->updateScreenItem(*_screenItem); - g_sci->getSciDebugger()->onFrame(); g_sci->_gfxFrameout->frameOut(true); - g_sci->_gfxFrameout->throttle(); + g_sci->getSciDebugger()->onFrame(); } } @@ -891,7 +889,7 @@ void VMDPlayer::restrictPalette(const uint8 startColor, const int16 endColor) { // 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); + _endColor = MIN<int16>(255, endColor); } } // End of namespace Sci diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 2e69932e49..8826b0625a 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -159,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 @@ -948,7 +948,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) { diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index cbc4a02739..4b3a13c490 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -965,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; @@ -1007,6 +1011,7 @@ void ResourceManager::changeAudioDirectory(Common::String path) { // Rescan the newly added resources scanNewSources(); +#endif } } // End of namespace Sci diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 86c0cffe15..246c031c06 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -128,6 +128,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam 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"); @@ -494,62 +495,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; @@ -564,9 +592,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: @@ -593,17 +624,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); } } } @@ -627,10 +662,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; } @@ -955,6 +992,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 @@ -1065,11 +1121,17 @@ void SciEngine::syncIngameAudioOptions() { bool subtitlesOn = ConfMan.getBool("subtitles"); bool speechOn = !ConfMan.getBool("speech_mute"); +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + _gamestate->variables[VAR_GLOBAL][kGlobalVarTextSpeed] = make_reg(0, 8 - ConfMan.getInt("talkspeed") * 8 / 255); + } +#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) { @@ -1090,11 +1152,11 @@ void SciEngine::syncIngameAudioOptions() { // 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 } } } @@ -1105,7 +1167,7 @@ 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(); + 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 b336eb8cce..b46207b075 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -125,7 +125,8 @@ enum kDebugLevels { kDebugLevelDebugMode = 1 << 21, kDebugLevelScriptPatcher = 1 << 22, kDebugLevelWorkarounds = 1 << 23, - kDebugLevelVideo = 1 << 24 + kDebugLevelVideo = 1 << 24, + kDebugLevelGame = 1 << 25 }; enum SciGameId { diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp index 4af474b918..659a5265d4 100644 --- a/engines/sci/sound/audio32.cpp +++ b/engines/sci/sound/audio32.cpp @@ -183,6 +183,9 @@ int Audio32::writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::R 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(); } @@ -453,7 +456,11 @@ void Audio32::freeUnusedChannels() { const AudioChannel &channel = getChannel(channelIndex); if (!channel.robot && channel.stream->endOfStream()) { if (channel.loop) { - dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->rewind(); + 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--); } @@ -658,6 +665,9 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool 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); @@ -764,7 +774,12 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool // use audio streams, and allocate and fill the monitoring buffer // when reading audio data from the stream. - channel.duration = /* round up */ 1 + (dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->getLength().msecs() * 60 / 1000); + 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; @@ -1012,7 +1027,7 @@ int16 Audio32::getVolume(const int16 channelIndex) const { } void Audio32::setVolume(const int16 channelIndex, int16 volume) { - volume = MIN((int16)kMaxVolume, 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); diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp index 1757088ea4..446b986581 100644 --- a/engines/sci/video/robot_decoder.cpp +++ b/engines/sci/video/robot_decoder.cpp @@ -445,7 +445,7 @@ void RobotDecoder::initVideo(const int16 x, const int16 y, const int16 scale, co if (scale != 128) { _scaleInfo.x = scale; _scaleInfo.y = scale; - _scaleInfo.signal = kScaleSignalDoScaling32; + _scaleInfo.signal = kScaleSignalManual; } _plane = g_sci->_gfxFrameout->getPlanes().findByObject(plane); @@ -703,7 +703,7 @@ void RobotDecoder::showFrame(const uint16 frameNo, const uint16 newX, const uint const int16 lowResX = (scaledX * screenToLowResX).toInt(); const int16 lowResY = (scaledY2 * screenToLowResY).toInt(); - bitmap.setDisplace(Common::Point( + bitmap.setOrigin(Common::Point( (scaledX - (lowResX * lowResToScreenX).toInt()) * -1, (lowResY * lowResToScreenY).toInt() - scaledY1 )); @@ -713,7 +713,7 @@ void RobotDecoder::showFrame(const uint16 frameNo, const uint16 newX, const uint } else { const int16 scaledX = _originalScreenItemX[i] + _position.x; const int16 scaledY = _originalScreenItemY[i] + _position.y + bitmap.getHeight() - 1; - bitmap.setDisplace(Common::Point(0, bitmap.getHeight() - 1)); + bitmap.setOrigin(Common::Point(0, bitmap.getHeight() - 1)); _screenItemX[i] = scaledX; _screenItemY[i] = scaledY; } @@ -1391,7 +1391,7 @@ void RobotDecoder::doVersion5(const bool shouldSubmitAudio) { // TODO: Version 6 robot? // int scaleXRemainder; - if (_scaleInfo.signal == kScaleSignalDoScaling32) { + if (_scaleInfo.signal == kScaleSignalManual) { position.x = (position.x * _scaleInfo.x) / 128; // TODO: Version 6 robot? // scaleXRemainder = (position.x * _scaleInfo.x) % 128; @@ -1461,7 +1461,7 @@ uint32 RobotDecoder::createCel5(const byte *rawVideoData, const int16 screenItem const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; - Common::Point displace; + Common::Point origin; if (scriptWidth == kLowResX && scriptHeight == kLowResY) { const Ratio lowResToScreenX(screenWidth, kLowResX); const Ratio lowResToScreenY(screenHeight, kLowResY); @@ -1475,22 +1475,22 @@ uint32 RobotDecoder::createCel5(const byte *rawVideoData, const int16 screenItem const int16 lowResX = (scaledX * screenToLowResX).toInt(); const int16 lowResY = (scaledY2 * screenToLowResY).toInt(); - displace.x = (scaledX - (lowResX * lowResToScreenX).toInt()) * -1; - displace.y = (lowResY * lowResToScreenY).toInt() - scaledY1; + 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, displace.x, displace.y, scaledX, screenWidth, scaledY2, screenHeight, scaledX - displace.x, scaledY2 - displace.y); + 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; - displace.x = 0; - displace.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, displace.x, displace.y); + 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; @@ -1500,9 +1500,9 @@ uint32 RobotDecoder::createCel5(const byte *rawVideoData, const int16 screenItem SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[screenItemIndex].bitmapId); assert(bitmap.getWidth() == celWidth && bitmap.getHeight() == celHeight); - assert(bitmap.getScaledWidth() == _xResolution && bitmap.getScaledHeight() == _yResolution); + assert(bitmap.getXResolution() == _xResolution && bitmap.getYResolution() == _yResolution); assert(bitmap.getHunkPaletteOffset() == (uint32)bitmap.getWidth() * bitmap.getHeight() + SciBitmap::getBitmapHeaderSize()); - bitmap.setDisplace(displace); + bitmap.setOrigin(origin); byte *targetBuffer = nullptr; if (_verticalScaleFactor == 100) { |
