diff options
Diffstat (limited to 'engines/sci')
48 files changed, 1186 insertions, 527 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 565e9752c3..e233c4cba4 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -485,6 +485,7 @@ bool Console::cmdGetVersion(int argc, const char **argv) { #endif debugPrintf("View type: %s\n", viewTypeDesc[g_sci->getResMan()->getViewType()]); debugPrintf("Uses palette merging: %s\n", g_sci->_gfxPalette->isMerging() ? "yes" : "no"); + debugPrintf("Uses 16 bit color matching: %s\n", g_sci->_gfxPalette->isUsing16bitColorMatch() ? "yes" : "no"); debugPrintf("Resource volume version: %s\n", g_sci->getResMan()->getVolVersionDesc()); debugPrintf("Resource map version: %s\n", g_sci->getResMan()->getMapVersionDesc()); debugPrintf("Contains selector vocabulary (vocab.997): %s\n", hasVocab997 ? "yes" : "no"); @@ -663,7 +664,7 @@ bool Console::cmdDiskDump(int argc, const char **argv) { int resNumFrom = 0; int resNumTo = 0; int resNumCur = 0; - + if (argc != 3) { debugPrintf("Dumps the specified resource to disk as a patch file\n"); debugPrintf("Usage: %s <resource type> <resource number>\n", argv[0]); @@ -671,7 +672,7 @@ bool Console::cmdDiskDump(int argc, const char **argv) { cmdResourceTypes(argc, argv); return true; } - + if (strcmp(argv[2], "*") == 0) { resNumFrom = 0; resNumTo = 65535; diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index 4f28738508..85ff1c0062 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -563,31 +563,28 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, // If these files aren't found, it can't be SCI - if (!foundResMap && !foundRes000) { + if (!foundResMap && !foundRes000) return 0; - } ResourceManager resMan; - resMan.addAppropriateSources(fslist); - resMan.init(true); + resMan.addAppropriateSourcesForDetection(fslist); + resMan.initForDetection(); // TODO: Add error handling. #ifndef ENABLE_SCI32 // Is SCI32 compiled in? If not, and this is a SCI32 game, // stop here - if (getSciVersion() >= SCI_VERSION_2) { - return (const ADGameDescription *)&s_fallbackDesc; - } + if (getSciVersionForDetection() >= SCI_VERSION_2) + return 0; #endif ViewType gameViews = resMan.getViewType(); // Have we identified the game views? If not, stop here - // Can't be SCI (or unsupported SCI views). Pinball Creep by sierra also uses resource.map/resource.000 files - // but doesnt share sci format at all, if we dont return 0 here we will detect this game as SCI - if (gameViews == kViewUnknown) { + // Can't be SCI (or unsupported SCI views). Pinball Creep by Sierra also uses resource.map/resource.000 files + // but doesn't share SCI format at all + if (gameViews == kViewUnknown) return 0; - } // Set the platform to Amiga if the game is using Amiga views if (gameViews == kViewAmiga) @@ -597,9 +594,8 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, Common::String sierraGameId = resMan.findSierraGameId(); // If we don't have a game id, the game is not SCI - if (sierraGameId.empty()) { + if (sierraGameId.empty()) return 0; - } Common::String gameId = convertSierraGameId(sierraGameId, &s_fallbackDesc.flags, resMan); strncpy(s_fallbackGameIdBuf, gameId.c_str(), sizeof(s_fallbackGameIdBuf) - 1); diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 344298ce9a..32d1a58765 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -1832,15 +1832,12 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, -#if 0 - // The resource.002 file, contained in disk 3, is broken in this version - // (it contains a large chunk of zeroes and several broken resources, - // e.g. pic 250 and views 250 and 251). - // Thus this detection entry isn't accurate. - - // Larry 1 Remake - English Amiga (from www.back2roots.org) + // Larry 1 Remake - English Amiga // Executable scanning reports "1.004.024" // SCI interpreter version 1.000.784 + // NOTE: The resource.002 file, contained in disk 3, is broken in the + // www.back2roots.org version (it contains a large chunk of zeroes and + // several broken resources, e.g. pic 250 and views 250 and 251). {"lsl1sci", "SCI", { {"resource.map", 0, "7d115a9e27dc8ac71e8d5ef33d589bd5", 3366}, {"resource.000", 0, "e67fd129d5810fc7ad8ea509d891cc00", 363073}, @@ -1849,7 +1846,6 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.003", 0, "4a34c3367c2fe7eb380d741374da1989", 572251}, AD_LISTEND}, Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, -#endif // Larry 1 VGA Remake - English DOS (from spookypeanut) // Executable scanning reports "1.000.577", VERSION file reports "2.1" @@ -2073,6 +2069,20 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Larry 3 - German DOS (German+English, 5 1/4" floppies) + // SCI interpreter version S.old.114 (executable), VERSION is "1.056" + {"lsl3", "", { + {"resource.map", 0, "2468da5d664bb6ca3df866074a05e43c", 8910}, + {"resource.001", 0, "3827a9b17b926e12dcc336860f50612a", 163326}, + {"resource.002", 0, "3827a9b17b926e12dcc336860f50612a", 312436}, + {"resource.003", 0, "3827a9b17b926e12dcc336860f50612a", 347307}, + {"resource.004", 0, "3827a9b17b926e12dcc336860f50612a", 332369}, + {"resource.005", 0, "3827a9b17b926e12dcc336860f50612a", 347654}, + {"resource.006", 0, "3827a9b17b926e12dcc336860f50612a", 326011}, + {"resource.007", 0, "3827a9b17b926e12dcc336860f50612a", 309553}, + AD_LISTEND}, + Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Larry 3 - French DOS (provided by richiefs in bug report #2670691, also includes english language) // Executable scanning reports "S.old.123" // SCI interpreter version 0.000.572 (just a guess) @@ -2607,6 +2617,48 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Phantasmagoria - German DOS/Windows + // Windows executable scanning reports "unknown" - "Sep 19 1995 09:39:48" + // DOS executable scanning reports "unknown" - "Sep 19 1995 16:18:34" + // VERSION file reports "1.100.000" + // Supplied by AReim1982 + {"phantasmagoria", "", { + {"resmap.001", 0, "d5048f972d2e1abd5f6b6a3ea8a466b0", 11524}, + {"ressci.001", 0, "3aae6559aa1df273bc542d5ac6330d75", 71063082}, + {"resmap.002", 0, "ae0105261e04826324daf7dd2d534465", 12064}, + {"ressci.002", 0, "3aae6559aa1df273bc542d5ac6330d75", 80283368}, + {"resmap.003", 0, "50786a4f54c6432ec31b52be90b3a8ba", 12340}, + {"ressci.003", 0, "3aae6559aa1df273bc542d5ac6330d75", 82360370}, + {"resmap.004", 0, "4cd3bbff8b81bad85db52c0c8407bd81", 12562}, + {"ressci.004", 0, "3aae6559aa1df273bc542d5ac6330d75", 84453560}, + {"resmap.005", 0, "779bd12802da6cfe54ce482140824a46", 12616}, + {"ressci.005", 0, "3aae6559aa1df273bc542d5ac6330d75", 85113663}, + {"resmap.006", 0, "2299f97876493cc29b6a48e1cfe9619d", 12538}, + {"ressci.006", 0, "3aae6559aa1df273bc542d5ac6330d75", 87602346}, + {"resmap.007", 0, "06309b8043aecb85bd507b15d16cb544", 7984}, + //{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 26898681}, + AD_LISTEND}, + Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + + // Phantasmagoria - French DOS + // Supplied by Kervala in bug #6574 + {"phantasmagoria", "", { + {"resmap.001", 0, "4da82dd336d4b9cd8c16f3cc11f0c615", 11524}, + {"ressci.001", 0, "3aae6559aa1df273bc542d5ac6330d75", 69963685}, + {"resmap.002", 0, "4f40f43f2b60bf765864433069752bb9", 12064}, + {"ressci.002", 0, "3aae6559aa1df273bc542d5ac6330d75", 78362841}, + {"resmap.003", 0, "6a392a86f14b6ddb4422978ee71e54ac", 12340}, + {"ressci.003", 0, "3aae6559aa1df273bc542d5ac6330d75", 80431189}, + {"resmap.004", 0, "df2e9462c41202de5f3843908c95a715", 12562}, + {"ressci.004", 0, "3aae6559aa1df273bc542d5ac6330d75", 82542844}, + {"resmap.005", 0, "43efd3fe834286c70a2c8b4cd747c1e2", 12616}, + {"ressci.005", 0, "3aae6559aa1df273bc542d5ac6330d75", 83790486}, + {"resmap.006", 0, "b3065e54a00190752a06dacd201b5058", 12538}, + {"ressci.006", 0, "3aae6559aa1df273bc542d5ac6330d75", 85415107}, + {"resmap.007", 0, "5633960bc106c39ca91d2d8fce18fd2d", 7984}, + AD_LISTEND}, + Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Phantasmagoria - English DOS Demo // Executable scanning reports "2.100.002" {"phantasmagoria", "Demo", { @@ -2659,6 +2711,25 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Phantasmagoria 2 - German DOS/Windows + // Windows executable scanning reports "unknown" - "Dec 07 1996 15:42:02" + // DOS executable scanning reports "unknown" - "Dec 07 1996 08:35:12" + // VERSION file reports "000.1.0vu" (HEX: 30 30 30 2E 31 00 2E 30 76 FA 0D 0A) + // Supplied by AReim1982 + {"phantasmagoria2", "", { + {"resmap.001", 0, "d62f48ff8bddb39503b97e33439482c9", 1114}, + {"ressci.001", 0, "4ebc2b8455c74ad205ae592eec27313a", 24590716}, + {"resmap.002", 0, "642a1f85ad8a4ce1d3850b10ad082200", 1138}, + {"ressci.002", 0, "4ebc2b8455c74ad205ae592eec27313a", 34681672}, + {"resmap.003", 0, "e08316864ef77735bb7f8f208110c43b", 1174}, + {"ressci.003", 0, "4ebc2b8455c74ad205ae592eec27313a", 38930933}, + {"resmap.004", 0, "875cf07df77fbaa1518a06ffed616c5f", 1300}, + {"ressci.004", 0, "4ebc2b8455c74ad205ae592eec27313a", 42750325}, + {"resmap.005", 0, "2fc48a4a5a73b726994f189da51a8b2a", 1954}, + {"ressci.005", 0, "e94005890d22dd3b7f605a2a7c025803", 68232146}, + AD_LISTEND}, + Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Phantasmagoria 2 - English DOS (GOG version) - ressci.* merged in ressci.000 // Executable scanning reports "3.000.000" - "Dec 07 1996 09:29:03" // VERSION file reports "001.0.06" @@ -4100,6 +4171,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { FANMADE("Knight's Quest Demo 1.0", "5e816edf993956752ed06fccfeeae6d9", 1260, "959321f88a22905fa1f8c6d897874744", 703836), FANMADE("LockerGnome Quest", "3eeff9130206cad0c4e1551e2b9dd2c5", 420, "ae05ca90806fd90cc43f147c82d3547c", 158906), FANMADE("LockerGnome Quest Redux", "55b0081dbdd77a07807c76cec3606970", 492, "75c9c5e8a475a7b5f1a6cb18edad67f2", 168069), + FANMADE("LockerGnome Quest Redux", "6299578d8ab709cc181baea6b984a0a7", 492, "c0ff4bfcc62fb111337343967e4001fd", 167383), FANMADE("New Year's Mystery", "e4dcab1b1d3cb4a2c070a07a9c9589e0", 708, "e00ca5e44fd4e98d8174b467b31b0f21", 295425), FANMADE("New Year's Mystery (Updated)", "efd1beb5120293725065c95959144f81", 714, "b3bd3c2372ed6efa28adb12403c4c31a", 305027), FANMADE("Ocean Battle", "c2304a0568e0eb84f8e9a0915f01170a", 408, "46c520c1ac9b63528854d0f58c7e1b74", 142234), diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index 31e7ca4931..be062dba64 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -344,9 +344,9 @@ bool GameFeatures::autoDetectGfxFunctionsType(int methodNum) { if (kFuncNum == 8) { // kDrawPic (SCI0 - SCI11) // If kDrawPic is called with 6 parameters from the overlay // selector, the game is using old graphics functions. - // Otherwise, if it's called with 8 parameters, it's using new - // graphics functions. - _gfxFunctionsType = (argc == 8) ? SCI_VERSION_0_LATE : SCI_VERSION_0_EARLY; + // Otherwise, if it's called with 8 parameters (e.g. SQ3) or 4 parameters + // (e.g. Hoyle 1/2), it's using new graphics functions. + _gfxFunctionsType = (argc == 6) ? SCI_VERSION_0_EARLY : SCI_VERSION_0_LATE; return true; } } diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index dddf845222..a65bcb7df5 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -145,7 +145,7 @@ public: */ Kernel(ResourceManager *resMan, SegManager *segMan); ~Kernel(); - + void init(); uint getSelectorNamesSize() const; @@ -161,7 +161,7 @@ public: * @return The appropriate selector ID, or -1 on error */ int findSelector(const char *selectorName) const; - + bool selectorNamesAvailable(); // Script dissection/dumping functions diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index fc46d16b8d..0c2fd4e3ea 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -468,7 +468,8 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(TimesSin), SIG_EVERYWHERE, "ii", NULL, NULL }, { "SinMult", kTimesSin, SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(TimesTan), SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(UnLoad), SIG_EVERYWHERE, "i[ri]", NULL, kUnLoad_workarounds }, + { MAP_CALL(UnLoad), SIG_EVERYWHERE, "i[ir!]", NULL, kUnLoad_workarounds }, + // ^ We allow invalid references here (e.g. bug #6600), since they will be invalidated anyway by the call itself { MAP_CALL(ValidPath), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(Wait), SIG_EVERYWHERE, "i", NULL, NULL }, diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 61fb717567..c56eb09482 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -37,6 +37,7 @@ #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/savegame.h" +#include "sci/graphics/menu.h" #include "sci/sound/audio.h" #include "sci/console.h" @@ -913,6 +914,25 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { // code concerning this via script patch. s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); break; + case GID_JONES: + // HACK: The code that enables certain menu items isn't called when a game is restored from the + // launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723. + // These menu entries are disabled when the game is launched, and are enabled when a new game is + // started. The code for enabling these entries is is all in script 1, room1::init, but that code + // path is never followed in these two cases (restoring game from the menu, or restoring a game + // from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here. + // These two are needed when restoring from the launcher + // FIXME: The original interpreter saves and restores the menu state, so these attributes + // are automatically reset there. We may want to do the same. + g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> About Jones + g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> Help + // The rest are normally enabled from room1::init + g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Options -> Delete current player + g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game + g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Restore Game + g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics + g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals + break; default: break; } diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index c2089bcd4d..ee2249bd9d 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -354,13 +354,16 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { } textWidth = dest[3].toUint16(); textHeight = dest[2].toUint16(); + + uint16 languageSplitter = 0; + Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter, sep); #ifdef ENABLE_SCI32 if (g_sci->_gfxText32) - g_sci->_gfxText32->kernelTextSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight); + g_sci->_gfxText32->kernelTextSize(splitText.c_str(), font_nr, maxwidth, &textWidth, &textHeight); else #endif - g_sci->_gfxText16->kernelTextSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight); + g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight); // One of the game texts in LB2 German contains loads of spaces in // its end. We trim the text here, otherwise the graphics code will @@ -376,7 +379,7 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { // Copy over the trimmed string... s->_segMan->strcpy(argv[1], text.c_str()); // ...and recalculate bounding box dimensions - g_sci->_gfxText16->kernelTextSize(g_sci->strSplit(text.c_str(), sep).c_str(), font_nr, maxwidth, &textWidth, &textHeight); + g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight); } } @@ -818,16 +821,29 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { if (!textReference.isNull()) text = s->_segMan->getString(textReference); + uint16 languageSplitter = 0; + Common::String splitText; + + switch (type) { + case SCI_CONTROLS_TYPE_BUTTON: + case SCI_CONTROLS_TYPE_TEXTEDIT: + splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter, NULL); + break; + case SCI_CONTROLS_TYPE_TEXT: + splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter); + break; + } + switch (type) { case SCI_CONTROLS_TYPE_BUTTON: debugC(kDebugLevelGraphics, "drawing button %04x:%04x to %d,%d", PRINT_REG(controlObject), x, y); - g_sci->_gfxControls16->kernelDrawButton(rect, controlObject, g_sci->strSplit(text.c_str(), NULL).c_str(), fontId, style, hilite); + g_sci->_gfxControls16->kernelDrawButton(rect, controlObject, splitText.c_str(), languageSplitter, fontId, style, hilite); return; case SCI_CONTROLS_TYPE_TEXT: alignment = readSelectorValue(s->_segMan, controlObject, SELECTOR(mode)); debugC(kDebugLevelGraphics, "drawing text %04x:%04x ('%s') to %d,%d, mode=%d", PRINT_REG(controlObject), text.c_str(), x, y, alignment); - g_sci->_gfxControls16->kernelDrawText(rect, controlObject, g_sci->strSplit(text.c_str()).c_str(), fontId, alignment, style, hilite); + g_sci->_gfxControls16->kernelDrawText(rect, controlObject, splitText.c_str(), languageSplitter, fontId, alignment, style, hilite); s->r_acc = g_sci->_gfxText16->allocAndFillReferenceRectArray(); return; @@ -841,7 +857,7 @@ void _k_GenericDrawControl(EngineState *s, reg_t controlObject, bool hilite) { writeSelectorValue(s->_segMan, controlObject, SELECTOR(cursor), cursorPos); } debugC(kDebugLevelGraphics, "drawing edit control %04x:%04x (text %04x:%04x, '%s') to %d,%d", PRINT_REG(controlObject), PRINT_REG(textReference), text.c_str(), x, y); - g_sci->_gfxControls16->kernelDrawTextEdit(rect, controlObject, g_sci->strSplit(text.c_str(), NULL).c_str(), fontId, mode, style, cursorPos, maxChars, hilite); + g_sci->_gfxControls16->kernelDrawTextEdit(rect, controlObject, splitText.c_str(), languageSplitter, fontId, mode, style, cursorPos, maxChars, hilite); return; case SCI_CONTROLS_TYPE_ICON: @@ -1165,8 +1181,11 @@ reg_t kDisplay(EngineState *s, int argc, reg_t *argv) { argc--; argc--; argv++; argv++; text = g_sci->getKernel()->lookupText(textp, index); } + + uint16 languageSplitter = 0; + Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter); - return g_sci->_gfxPaint16->kernelDisplay(g_sci->strSplit(text.c_str()).c_str(), argc, argv); + return g_sci->_gfxPaint16->kernelDisplay(splitText.c_str(), languageSplitter, argc, argv); } reg_t kSetVideoMode(EngineState *s, int argc, reg_t *argv) { diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 56dad583e4..eef758a0d9 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -42,10 +42,12 @@ reg_t kStrCat(EngineState *s, int argc, reg_t *argv) { Common::String s1 = s->_segMan->getString(argv[0]); Common::String s2 = s->_segMan->getString(argv[1]); - // The Japanese version of PQ2 splits the two strings here - // (check bug #3396887). - if (g_sci->getGameId() == GID_PQ2 && - g_sci->getLanguage() == Common::JA_JPN) { + // Japanese PC-9801 interpreter splits strings here + // see bug #5834 + // Verified for Police Quest 2 + Quest For Glory 1 + // However Space Quest 4 PC-9801 doesn't + if ((g_sci->getLanguage() == Common::JA_JPN) + && (getSciVersion() <= SCI_VERSION_01)) { s1 = g_sci->strSplit(s1.c_str(), NULL); s2 = g_sci->strSplit(s2.c_str(), NULL); } diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index cdcdcc41e5..0b55425406 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -640,9 +640,11 @@ void SoundCommandParser::reconstructPlayList() { initSoundResource(*i); if ((*i)->status == kSoundPlaying) { - // Sync the sound object's selectors related to playing with the stored - // ones in the playlist, as they may have been invalidated when loading. - // Refer to bug #3104624. + // WORKAROUND: PQ3 (German?) scripts can set volume negative in the + // sound object directly without going through DoSound. + // Since we re-read this selector when re-playing the sound after loading, + // this will lead to unexpected behaviour. As a workaround we + // sync the sound object's selectors here. (See bug #5501) writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(loop), (*i)->loop); writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(priority), (*i)->priority); if (_soundVersion >= SCI_VERSION_1_EARLY) @@ -844,6 +846,8 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin if (voc) voc->saveLoadWithSerializer(ser); + // TODO: SSCI (at least JonesCD, presumably more) also stores the Menu state + return true; } diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 8bbbd713a6..03cd1d06e9 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -1214,6 +1214,26 @@ static const uint16 kq6CDPatchAudioTextSupportGirlInTheTower[] = { PATCH_END }; +// Fixes dual mode for scenes with Azure and Ariel (room 370) +// Effectively same patch as the one for fixing "Girl In The Tower" +// Applies to at least: PC-CD +// Patched methods: rm370::init, caughtAtGateCD::changeState, caughtAtGateTXT::changeState, toLabyrinth::changeState +// Fixes bug: #6750 +static const uint16 kq6CDSignatureAudioTextSupportAzureAriel[] = { + SIG_MAGICDWORD, + 0x89, 0x5a, // lsg global[5a] + 0x35, 0x02, // ldi 02 + 0x1a, // eq? + 0x31, // bnt [jump-for-text-code] + SIG_END +}; + +static const uint16 kq6CDPatchAudioTextSupportAzureAriel[] = { + PATCH_ADDTOOFFSET(+4), + 0x12, // and + PATCH_END +}; + // Additional patch specifically for King's Quest 6 // Adds another button state for the text/audio button. We currently use the "speech" view for "dual" mode. // View 947, loop 9, cel 0+1 -> "text" @@ -1306,6 +1326,7 @@ static const SciScriptPatcherEntry kq6Signatures[] = { { false, 1009, "CD: audio + text support KQ6 Guards", 2, kq6CDSignatureAudioTextSupportGuards, kq6CDPatchAudioTextSupportGuards }, { false, 1027, "CD: audio + text support KQ6 Stepmother", 1, kq6CDSignatureAudioTextSupportStepmother, kq6CDPatchAudioTextSupportJumpAlways }, { false, 740, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower }, + { false, 370, "CD: audio + text support KQ6 Azure & Ariel", 6, kq6CDSignatureAudioTextSupportAzureAriel, kq6CDPatchAudioTextSupportAzureAriel }, { false, 903, "CD: audio + text support KQ6 menu", 1, kq6CDSignatureAudioTextMenuSupport, kq6CDPatchAudioTextMenuSupport }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -1917,6 +1938,43 @@ static const SciScriptPatcherEntry pq1vgaSignatures[] = { }; // =========================================================================== +// At the healer's house there is a bird's nest up on the tree. +// The player can throw rocks at it until it falls to the ground. +// The hero will then grab the item, that is in the nest. +// +// When running is active, the hero will not reach the actual destination +// and because of that, the game will get stuck. +// +// We just change the coordinate of the destination slightly, so that walking, +// sneaking and running work. +// +// This bug was fixed by Sierra at least in the Japanese PC-9801 version. +// Applies to at least: English floppy (1.000, 1.012) +// Responsible method: pickItUp::changeState (script 54) +// Fixes bug: #6407 +static const uint16 qfg1egaSignatureThrowRockAtNest[] = { + 0x4a, 0x04, // send 04 (nest::x) + 0x36, // push + SIG_MAGICDWORD, + 0x35, 0x0f, // ldi 0f (15d) + 0x02, // add + 0x36, // push + SIG_END +}; + +static const uint16 qfg1egaPatchThrowRockAtNest[] = { + PATCH_ADDTOOFFSET(+3), + 0x35, 0x12, // ldi 12 (18d) + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry qfg1egaSignatures[] = { + { true, 54, "throw rock at nest while running", 1, qfg1egaSignatureThrowRockAtNest, qfg1egaPatchThrowRockAtNest }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +// =========================================================================== // script 215 of qfg1vga pointBox::doit actually processes button-presses // during fighting with monsters. It strangely also calls kGetEvent. Because // the main User::doit also calls kGetEvent it's pure luck, where the event @@ -3036,6 +3094,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_PQ1: signatureTable = pq1vgaSignatures; break; + case GID_QFG1: + signatureTable = qfg1egaSignatures; + break; case GID_QFG1VGA: signatureTable = qfg1vgaSignatures; break; diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h index 0b35792949..7023ef327e 100644 --- a/engines/sci/engine/script_patches.h +++ b/engines/sci/engine/script_patches.h @@ -98,7 +98,7 @@ private: void enablePatch(const SciScriptPatcherEntry *patchTable, const char *searchDescription); int32 findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize, bool isMacSci11); void applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset, bool isMacSci11); - + Selector *_selectorIdTable; SciScriptPatcherRuntimeEntry *_runtimeTable; }; diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index 3738fd3dcb..58c2b8d3e3 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -371,7 +371,7 @@ void SegManager::freeHunkEntry(reg_t addr) { HunkTable *ht = (HunkTable *)getSegment(addr.getSegment(), SEG_TYPE_HUNK); if (!ht) { - warning("Attempt to free Hunk from address %04x:%04x: Invalid segment type", PRINT_REG(addr)); + warning("Attempt to free Hunk from address %04x:%04x: Invalid segment type %d", PRINT_REG(addr), getSegmentType(addr.getSegment())); return; } diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 7701822f6d..527c8f0ae0 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -203,59 +203,94 @@ static kLanguage charToLanguage(const char c) { } } -Common::String SciEngine::getSciLanguageString(const Common::String &str, kLanguage lang, kLanguage *lang2) const { - kLanguage secondLang = K_LANG_NONE; - - const char *seeker = str.c_str(); - while (*seeker) { - if ((*seeker == '%') || (*seeker == '#')) { - secondLang = charToLanguage(*(seeker + 1)); - - if (secondLang != K_LANG_NONE) +Common::String SciEngine::getSciLanguageString(const Common::String &str, kLanguage requestedLanguage, kLanguage *secondaryLanguage, uint16 *languageSplitter) const { + kLanguage foundLanguage = K_LANG_NONE; + const byte *textPtr = (const byte *)str.c_str(); + byte curChar = 0; + byte curChar2 = 0; + + while (1) { + curChar = *textPtr; + if (!curChar) + break; + + if ((curChar == '%') || (curChar == '#')) { + curChar2 = *(textPtr + 1); + foundLanguage = charToLanguage(curChar2); + + if (foundLanguage != K_LANG_NONE) { + // Return language splitter + if (languageSplitter) + *languageSplitter = curChar | ( curChar2 << 8 ); + // Return the secondary language found in the string + if (secondaryLanguage) + *secondaryLanguage = foundLanguage; break; + } } - - ++seeker; + textPtr++; } - // Return the secondary language found in the string - if (lang2) - *lang2 = secondLang; - - if (secondLang == lang) { - if (*(++seeker) == 'J') { + if (foundLanguage == requestedLanguage) { + if (curChar2 == 'J') { // Japanese including Kanji, displayed with system font // Convert half-width characters to full-width equivalents Common::String fullWidth; - byte c; + uint16 mappedChar; + + textPtr += 2; // skip over language splitter + + while (1) { + curChar = *textPtr; + + switch (curChar) { + case 0: // Terminator NUL + return fullWidth; + case '\\': + // "\n", "\N", "\r" and "\R" were overwritten with SPACE + 0x0D in PC-9801 SSCI + // inside GetLongest() (text16). We do it here, because it's much cleaner and + // we have to process the text here anyway. + // Occurs for example in Police Quest 2 intro + curChar2 = *(textPtr + 1); + switch (curChar2) { + case 'n': + case 'N': + case 'r': + case 'R': + fullWidth += ' '; + fullWidth += 0x0D; // CR + textPtr += 2; + continue; + } + } + + textPtr++; - while ((c = *(++seeker))) { - uint16 mappedChar = s_halfWidthSJISMap[c]; + mappedChar = s_halfWidthSJISMap[curChar]; if (mappedChar) { fullWidth += mappedChar >> 8; fullWidth += mappedChar & 0xFF; } else { // Copy double-byte character - char c2 = *(++seeker); - if (!c2) { - error("SJIS character %02X is missing second byte", c); + curChar2 = *(textPtr++); + if (!curChar) { + error("SJIS character %02X is missing second byte", curChar); break; } - fullWidth += c; - fullWidth += c2; + fullWidth += curChar; + fullWidth += curChar2; } } - return fullWidth; } else { - return Common::String(seeker + 1); + return Common::String((const char *)(textPtr + 2)); } } - if (*seeker) - return Common::String(str.c_str(), seeker - str.c_str()); - else - return str; + if (curChar) + return Common::String(str.c_str(), (const char *)textPtr - str.c_str()); + + return str; } kLanguage SciEngine::getSciLanguage() { @@ -314,25 +349,25 @@ void SciEngine::setSciLanguage() { setSciLanguage(getSciLanguage()); } -Common::String SciEngine::strSplit(const char *str, const char *sep) { - kLanguage lang = getSciLanguage(); - kLanguage subLang = K_LANG_NONE; +Common::String SciEngine::strSplitLanguage(const char *str, uint16 *languageSplitter, const char *sep) { + kLanguage activeLanguage = getSciLanguage(); + kLanguage subtitleLanguage = K_LANG_NONE; if (SELECTOR(subtitleLang) != -1) - subLang = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(subtitleLang)); + subtitleLanguage = (kLanguage)readSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(subtitleLang)); - kLanguage secondLang; - Common::String retval = getSciLanguageString(str, lang, &secondLang); + kLanguage foundLanguage; + Common::String retval = getSciLanguageString(str, activeLanguage, &foundLanguage, languageSplitter); // Don't add subtitle when separator is not set, subtitle language is not set, or // string contains only one language - if ((sep == NULL) || (subLang == K_LANG_NONE) || (secondLang == K_LANG_NONE)) + if ((sep == NULL) || (subtitleLanguage == K_LANG_NONE) || (foundLanguage == K_LANG_NONE)) return retval; // Add subtitle, unless the subtitle language doesn't match the languages in the string - if ((subLang == K_LANG_ENGLISH) || (subLang == secondLang)) { + if ((subtitleLanguage == K_LANG_ENGLISH) || (subtitleLanguage == foundLanguage)) { retval += sep; - retval += getSciLanguageString(str, subLang); + retval += getSciLanguageString(str, subtitleLanguage); } return retval; diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 37e46b7a96..c5730b5345 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -44,7 +44,8 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = { { GID_MOTHERGOOSEHIRES,90, 90, 0, "newGameButton", "select", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_ge: MUMG Deluxe, when selecting "New Game" in the main menu. It tries to compare an integer with a list. Needs to return false for the game to continue. { GID_PHANTASMAGORIA, 902, 0, 0, "", "export 7", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_shr: when starting a chapter in Phantasmagoria { GID_QFG1VGA, 301, 928, 0, "Blink", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_div: when entering the inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object - { GID_QFG2, 200, 200, 0, "astro", "messages", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer bug #5152 + { GID_QFG2, 200, 200, 0, "astro", "messages", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer - bug #5152 + { GID_QFG3, 780, 999, 0, "", "export 6", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_add: trying to talk to yourself at the top of the giant tree - bug #6692 { GID_QFG4, 710,64941, 0, "RandCycle", "doit", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -90,7 +91,8 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_HOYLE4, 700, -1, 1, "BridgeDefense", "think", -1, -1, { WORKAROUND_FAKE, 0 } }, // sometimes while playing bridge, temp var 3, 17 and others, objects LeadReturn_Trump, ThirdSeat_Trump and others { GID_HOYLE4, 700, 730, 1, "BridgeDefense", "beatTheirBest", -1, 3, { WORKAROUND_FAKE, 0 } }, // rarely while playing bridge { GID_HOYLE4, 700, -1, 1, "Code", "doit", -1, -1, { WORKAROUND_FAKE, 0 } }, // when placing a bid in bridge (always), temp var 11, 24, 27, 46, 75, objects compete_tree, compwe_tree, other1_tree, b1 - bugs #5663 and #5794 - { GID_HOYLE4, 700, 921, 0, "Print", "addEdit", -1, -1, { WORKAROUND_FAKE, 0 } }, // when saving the game (may also occur in other situations) - bug #6601 + { GID_HOYLE4, 700, 921, 0, "Print", "addEdit", -1, 0, { WORKAROUND_FAKE, 118 } }, // when saving the game (may also occur in other situations) - bug #6601, bug #6614 + { GID_HOYLE4, 700, 921, 0, "Print", "addEdit", -1, 1, { WORKAROUND_FAKE, 1 } }, // see above, Text-control saves its coordinates to temp[0] and temp[1], Edit-control adjusts to those uninitialized temps, who by accident were left over from the Text-control { GID_HOYLE4, 300, 300, 0, "", "export 2", 0x1d4d, 0, { WORKAROUND_FAKE, 0 } }, // after passing around cards in hearts { GID_HOYLE4, 400, 400, 1, "GinHand", "calcRuns", -1, 4, { WORKAROUND_FAKE, 0 } }, // sometimes while playing Gin Rummy (e.g. when knocking and placing a card) - bug #5665 { GID_HOYLE4, 500, 17, 1, "Character", "say", -1, 504, { WORKAROUND_FAKE, 0 } }, // sometimes while playing Cribbage (e.g. when the opponent says "Last Card") - bug #5662 diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp index 511d2014bd..b1c002413d 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -262,7 +262,7 @@ SciEvent EventManager::getScummVMEvent() { // Scancodify if appropriate if (modifiers & Common::KBD_ALT) input.character = altify(input.character); - else if ((modifiers & Common::KBD_CTRL) && input.character > 0 && input.character < 27) + if (getSciVersion() <= SCI_VERSION_1_MIDDLE && (modifiers & Common::KBD_CTRL) && input.character > 0 && input.character < 27) input.character += 96; // 0x01 -> 'a' // If no actual key was pressed (e.g. if only a modifier key was pressed), diff --git a/engines/sci/graphics/controls16.cpp b/engines/sci/graphics/controls16.cpp index f2b2ccdfe6..e2e250cf9d 100644 --- a/engines/sci/graphics/controls16.cpp +++ b/engines/sci/graphics/controls16.cpp @@ -280,7 +280,7 @@ int GfxControls16::getPicNotValid() { return _screen->_picNotValid; } -void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 style, bool hilite) { +void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 style, bool hilite) { int16 sci0EarlyPen = 0, sci0EarlyBack = 0; if (!hilite) { if (getSciVersion() == SCI_VERSION_0_EARLY) { @@ -295,7 +295,7 @@ void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *t _paint16->frameRect(rect); rect.grow(-2); _ports->textGreyedOutput(!(style & SCI_CONTROLS_STYLE_ENABLED)); - _text16->Box(text, false, rect, SCI_TEXT16_ALIGNMENT_CENTER, fontId); + _text16->Box(text, languageSplitter, false, rect, SCI_TEXT16_ALIGNMENT_CENTER, fontId); _ports->textGreyedOutput(false); rect.grow(1); if (style & SCI_CONTROLS_STYLE_SELECTED) @@ -318,12 +318,12 @@ void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *t } } -void GfxControls16::kernelDrawText(Common::Rect rect, reg_t obj, const char *text, int16 fontId, TextAlignment alignment, int16 style, bool hilite) { +void GfxControls16::kernelDrawText(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, TextAlignment alignment, int16 style, bool hilite) { if (!hilite) { rect.grow(1); _paint16->eraseRect(rect); rect.grow(-1); - _text16->Box(text, false, rect, alignment, fontId); + _text16->Box(text, languageSplitter, false, rect, alignment, fontId); if (style & SCI_CONTROLS_STYLE_SELECTED) { _paint16->frameRect(rect); } @@ -335,7 +335,7 @@ void GfxControls16::kernelDrawText(Common::Rect rect, reg_t obj, const char *tex } } -void GfxControls16::kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite) { +void GfxControls16::kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite) { Common::Rect textRect = rect; uint16 oldFontId = _text16->GetFontId(); @@ -343,7 +343,7 @@ void GfxControls16::kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char _texteditCursorVisible = false; texteditCursorErase(); _paint16->eraseRect(rect); - _text16->Box(text, false, textRect, SCI_TEXT16_ALIGNMENT_LEFT, fontId); + _text16->Box(text, languageSplitter, false, textRect, SCI_TEXT16_ALIGNMENT_LEFT, fontId); _paint16->frameRect(rect); if (style & SCI_CONTROLS_STYLE_SELECTED) { _text16->SetFont(fontId); diff --git a/engines/sci/graphics/controls16.h b/engines/sci/graphics/controls16.h index 6a70c71aae..39ffa243fb 100644 --- a/engines/sci/graphics/controls16.h +++ b/engines/sci/graphics/controls16.h @@ -55,9 +55,9 @@ public: GfxControls16(SegManager *segMan, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen); ~GfxControls16(); - void kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 style, bool hilite); - void kernelDrawText(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 alignment, int16 style, bool hilite); - void kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite); + void kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 style, bool hilite); + void kernelDrawText(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 alignment, int16 style, bool hilite); + void kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite); void kernelDrawIcon(Common::Rect rect, reg_t obj, GuiResourceId viewId, int16 loopNo, int16 celNo, int16 priority, int16 style, bool hilite); void kernelDrawList(Common::Rect rect, reg_t obj, int16 maxChars, int16 count, const char **entries, GuiResourceId fontId, int16 style, int16 upperPos, int16 cursorPos, bool isAlias, bool hilite); void kernelTexteditChange(reg_t controlObject, reg_t eventObject); diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp index 048ec1e9b9..1a58de073c 100644 --- a/engines/sci/graphics/cursor.cpp +++ b/engines/sci/graphics/cursor.cpp @@ -47,7 +47,7 @@ GfxCursor::GfxCursor(ResourceManager *resMan, GfxPalette *palette, GfxScreen *sc _isVisible = true; // center mouse cursor - setPosition(Common::Point(_screen->getWidth() / 2, _screen->getHeight() / 2)); + setPosition(Common::Point(_screen->getScriptWidth() / 2, _screen->getScriptHeight() / 2)); _moveZoneActive = false; _zoomZoneActive = false; @@ -151,14 +151,14 @@ void GfxCursor::kernelSetShape(GuiResourceId resourceId) { colorMapping[0] = 0; // Black is hardcoded colorMapping[1] = _screen->getColorWhite(); // White is also hardcoded colorMapping[2] = SCI_CURSOR_SCI0_TRANSPARENCYCOLOR; - colorMapping[3] = _palette->matchColor(170, 170, 170); // Grey + colorMapping[3] = _palette->matchColor(170, 170, 170) & SCI_PALETTE_MATCH_COLORMASK; // Grey // TODO: Figure out if the grey color is hardcoded // HACK for the magnifier cursor in LB1, fixes its color (bug #3487092) if (g_sci->getGameId() == GID_LAURABOW && resourceId == 1) colorMapping[3] = _screen->getColorWhite(); // HACK for Longbow cursors, fixes the shade of grey they're using (bug #3489101) if (g_sci->getGameId() == GID_LONGBOW) - colorMapping[3] = _palette->matchColor(223, 223, 223); // Light Grey + colorMapping[3] = _palette->matchColor(223, 223, 223) & SCI_PALETTE_MATCH_COLORMASK; // Light Grey // Seek to actual data resourceData += 4; @@ -481,7 +481,7 @@ void GfxCursor::kernelSetPos(Common::Point pos) { void GfxCursor::kernelMoveCursor(Common::Point pos) { _coordAdjuster->moveCursor(pos); - if (pos.x > _screen->getWidth() || pos.y > _screen->getHeight()) { + if (pos.x > _screen->getScriptWidth() || pos.y > _screen->getScriptHeight()) { warning("attempt to place cursor at invalid coordinates (%d, %d)", pos.y, pos.x); return; } diff --git a/engines/sci/graphics/font.cpp b/engines/sci/graphics/font.cpp index e4684ff134..2268ec0459 100644 --- a/engines/sci/graphics/font.cpp +++ b/engines/sci/graphics/font.cpp @@ -48,8 +48,8 @@ GfxFontFromResource::GfxFontFromResource(ResourceManager *resMan, GfxScreen *scr // filling info for every char for (int16 i = 0; i < _numChars; i++) { _chars[i].offset = READ_SCI32ENDIAN_UINT16(_resourceData + 6 + i * 2); - _chars[i].w = _resourceData[_chars[i].offset]; - _chars[i].h = _resourceData[_chars[i].offset + 1]; + _chars[i].width = _resourceData[_chars[i].offset]; + _chars[i].height = _resourceData[_chars[i].offset + 1]; } } @@ -66,10 +66,10 @@ byte GfxFontFromResource::getHeight() { return _fontHeight; } byte GfxFontFromResource::getCharWidth(uint16 chr) { - return chr < _numChars ? _chars[chr].w : 0; + return chr < _numChars ? _chars[chr].width : 0; } byte GfxFontFromResource::getCharHeight(uint16 chr) { - return chr < _numChars ? _chars[chr].h : 0; + return chr < _numChars ? _chars[chr].height : 0; } byte *GfxFontFromResource::getCharData(uint16 chr) { return chr < _numChars ? _resourceData + _chars[chr].offset + 2 : 0; diff --git a/engines/sci/graphics/font.h b/engines/sci/graphics/font.h index 58b2ba4813..451261f315 100644 --- a/engines/sci/graphics/font.h +++ b/engines/sci/graphics/font.h @@ -71,9 +71,11 @@ private: byte *_resourceData; struct Charinfo { - byte w, h; + byte width; + byte height; int16 offset; }; + byte _fontHeight; uint16 _numChars; Charinfo *_chars; diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index a322eb8e61..ccc362dc37 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -524,6 +524,10 @@ void GfxFrameout::showVideo() { RobotDecoder *videoDecoder = g_sci->_robotDecoder; uint16 x = videoDecoder->getPos().x; uint16 y = videoDecoder->getPos().y; + uint16 screenWidth = _screen->getWidth(); + uint16 screenHeight = _screen->getHeight(); + uint16 outputWidth; + uint16 outputHeight; if (videoDecoder->hasDirtyPalette()) g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256); @@ -532,7 +536,11 @@ void GfxFrameout::showVideo() { if (videoDecoder->needsUpdate()) { const Graphics::Surface *frame = videoDecoder->decodeNextFrame(); if (frame) { - g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, frame->w, frame->h); + // We need to clip here + // At least Phantasmagoria shows a 640x390 video on a 630x450 screen during the intro + outputWidth = frame->w > screenWidth ? screenWidth : frame->w; + outputHeight = frame->h > screenHeight ? screenHeight : frame->h; + g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, outputWidth, outputHeight); if (videoDecoder->hasDirtyPalette()) g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256); @@ -782,11 +790,12 @@ void GfxFrameout::kernelFrameout() { // TODO: For some reason, the top left nsRect coordinates get // swapped in the GK1 inventory screen, investigate why. - // HACK: Fix the coordinates by explicitly setting them here. - Common::Rect objNSRect = g_sci->_gfxCompare->getNSRect(itemEntry->object); - if (objNSRect.top == nsRect.left && objNSRect.left == nsRect.top && nsRect.top != 0 && nsRect.left != 0) { + // This is also needed for GK1 rooms 710 and 720 (catacombs, inner and + // outer circle), for handling the tiles and talking to Wolfgang. + // HACK: Fix the coordinates by explicitly setting them here for GK1. + // Also check bug #6729, for another case where this is needed. + if (g_sci->getGameId() == GID_GK1) g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect); - } } // Don't attempt to draw sprites that are outside the visible diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp index b835eb92ca..f80703e14d 100644 --- a/engines/sci/graphics/paint16.cpp +++ b/engines/sci/graphics/paint16.cpp @@ -476,7 +476,7 @@ void GfxPaint16::kernelGraphRedrawBox(Common::Rect rect) { #define SCI_DISPLAY_DUMMY3 117 #define SCI_DISPLAY_DONTSHOWBITS 121 -reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { +reg_t GfxPaint16::kernelDisplay(const char *text, uint16 languageSplitter, int argc, reg_t *argv) { reg_t displayArg; TextAlignment alignment = SCI_TEXT16_ALIGNMENT_LEFT; int16 colorPen = -1, colorBack = -1, width = -1, bRedraw = 1; @@ -572,7 +572,7 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { } // now drawing the text - _text16->Size(rect, text, -1, width); + _text16->Size(rect, text, languageSplitter, -1, width); rect.moveTo(_ports->getPort()->curLeft, _ports->getPort()->curTop); // Note: This code has been found in SCI1 middle and newer games. It was // previously only for SCI1 late and newer, but the LSL1 interpreter contains @@ -588,7 +588,7 @@ reg_t GfxPaint16::kernelDisplay(const char *text, int argc, reg_t *argv) { result = bitsSave(rect, GFX_SCREEN_MASK_VISUAL); if (colorBack != -1) fillRect(rect, GFX_SCREEN_MASK_VISUAL, colorBack, 0, 0); - _text16->Box(text, false, rect, alignment, -1); + _text16->Box(text, languageSplitter, false, rect, alignment, -1); if (_screen->_picNotValid == 0 && bRedraw) bitsShow(rect); // restoring port and cursor pos diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h index 882f311a5b..955cfdec8f 100644 --- a/engines/sci/graphics/paint16.h +++ b/engines/sci/graphics/paint16.h @@ -80,7 +80,7 @@ public: void kernelGraphUpdateBox(const Common::Rect &rect, bool hiresMode); void kernelGraphRedrawBox(Common::Rect rect); - reg_t kernelDisplay(const char *text, int argc, reg_t *argv); + reg_t kernelDisplay(const char *text, uint16 languageSplitter, int argc, reg_t *argv); reg_t kernelPortraitLoad(const Common::String &resourceName); void kernelPortraitShow(const Common::String &resourceName, Common::Point position, uint16 resourceNum, uint16 noun, uint16 verb, uint16 cond, uint16 seq); diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp index 7d106b5b02..a210a469f1 100644 --- a/engines/sci/graphics/paint32.cpp +++ b/engines/sci/graphics/paint32.cpp @@ -46,7 +46,7 @@ void GfxPaint32::fillRect(Common::Rect rect, byte color) { Common::Rect clipRect = rect; clipRect.clip(_screen->getWidth(), _screen->getHeight()); - + for (y = clipRect.top; y < clipRect.bottom; y++) { for (x = clipRect.left; x < clipRect.right; x++) { _screen->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0, 0); diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp index a3624c7959..59abef5550 100644 --- a/engines/sci/graphics/palette.cpp +++ b/engines/sci/graphics/palette.cpp @@ -65,14 +65,21 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen) // the real merging done in earlier games. If we use the copying over, we // will get issues because some views have marked all colors as being used // and those will overwrite the current palette in that case - if (getSciVersion() < SCI_VERSION_1_1) + if (getSciVersion() < SCI_VERSION_1_1) { _useMerging = true; - else if (getSciVersion() == SCI_VERSION_1_1) + _use16bitColorMatch = true; + } else if (getSciVersion() == SCI_VERSION_1_1) { // there are some games that use inbetween SCI1.1 interpreter, so we have // to detect if the current game is merging or copying _useMerging = _resMan->detectPaletteMergingSci11(); - else // SCI32 + _use16bitColorMatch = _useMerging; + // Note: Laura Bow 2 floppy uses the new palette format and is detected + // as 8 bit color matching because of that. + } else { + // SCI32 _useMerging = false; + _use16bitColorMatch = false; // not verified that SCI32 uses 8-bit color matching + } palVaryInit(); @@ -120,6 +127,10 @@ bool GfxPalette::isMerging() { return _useMerging; } +bool GfxPalette::isUsing16bitColorMatch() { + return _use16bitColorMatch; +} + // meant to get called only once during init of engine void GfxPalette::setDefault() { if (_resMan->getViewType() == kViewEga) @@ -464,8 +475,8 @@ bool GfxPalette::merge(Palette *newPalette, bool force, bool forceRealMerge) { // check if exact color could be matched res = matchColor(newPalette->colors[i].r, newPalette->colors[i].g, newPalette->colors[i].b); - if (res & 0x8000) { // exact match was found - newPalette->mapping[i] = res & 0xFF; + if (res & SCI_PALETTE_MATCH_PERFECT) { // exact match was found + newPalette->mapping[i] = res & SCI_PALETTE_MATCH_COLORMASK; continue; } @@ -486,8 +497,8 @@ bool GfxPalette::merge(Palette *newPalette, bool force, bool forceRealMerge) { // if still no luck - set an approximate color if (j == 256) { - newPalette->mapping[i] = res & 0xFF; - _sysPalette.colors[res & 0xFF].used |= 0x10; + newPalette->mapping[i] = res & SCI_PALETTE_MATCH_COLORMASK; + _sysPalette.colors[res & SCI_PALETTE_MATCH_COLORMASK].used |= 0x10; } } @@ -509,29 +520,47 @@ void GfxPalette::drewPicture(GuiResourceId pictureId) { } } -uint16 GfxPalette::matchColor(byte r, byte g, byte b) { - byte found = 0xFF; - int diff = 0x2FFFF, cdiff; - int16 dr,dg,db; - - for (int i = 1; i < 255; i++) { - if ((!_sysPalette.colors[i].used)) - continue; - dr = _sysPalette.colors[i].r - r; - dg = _sysPalette.colors[i].g - g; - db = _sysPalette.colors[i].b - b; -// minimum squares match - cdiff = (dr*dr) + (dg*dg) + (db*db); -// minimum sum match (Sierra's) -// cdiff = ABS(dr) + ABS(dg) + ABS(db); - if (cdiff < diff) { - if (cdiff == 0) - return i | 0x8000; // setting this flag to indicate exact match - found = i; - diff = cdiff; +uint16 GfxPalette::matchColor(byte matchRed, byte matchGreen, byte matchBlue) { + int16 colorNr; + int16 differenceRed, differenceGreen, differenceBlue; + int16 differenceTotal = 0; + int16 bestDifference = 0x7FFF; + uint16 bestColorNr = 255; + + if (_use16bitColorMatch) { + // used by SCI0 to SCI1, also by the first few SCI1.1 games + for (colorNr = 0; colorNr < 256; colorNr++) { + if ((!_sysPalette.colors[colorNr].used)) + continue; + differenceRed = ABS(_sysPalette.colors[colorNr].r - matchRed); + differenceGreen = ABS(_sysPalette.colors[colorNr].g - matchGreen); + differenceBlue = ABS(_sysPalette.colors[colorNr].b - matchBlue); + differenceTotal = differenceRed + differenceGreen + differenceBlue; + if (differenceTotal <= bestDifference) { + bestDifference = differenceTotal; + bestColorNr = colorNr; + } + } + } else { + // SCI1.1, starting with QfG3 introduced a bug in the matching code + // we have to implement it as well, otherwise some colors will be "wrong" in comparison to the original interpreter + // See Space Quest 5 bug #6455 + for (colorNr = 0; colorNr < 256; colorNr++) { + if ((!_sysPalette.colors[colorNr].used)) + continue; + differenceRed = (uint8)ABS<int8>(_sysPalette.colors[colorNr].r - matchRed); + differenceGreen = (uint8)ABS<int8>(_sysPalette.colors[colorNr].g - matchGreen); + differenceBlue = (uint8)ABS<int8>(_sysPalette.colors[colorNr].b - matchBlue); + differenceTotal = differenceRed + differenceGreen + differenceBlue; + if (differenceTotal <= bestDifference) { + bestDifference = differenceTotal; + bestColorNr = colorNr; + } } } - return found; + if (differenceTotal == 0) // original interpreter does not do this, instead it does 2 calls for merges in the worst case + return bestColorNr | SCI_PALETTE_MATCH_PERFECT; // we set this flag, so that we can optimize during palette merge + return bestColorNr; } void GfxPalette::getSys(Palette *pal) { @@ -621,7 +650,7 @@ void GfxPalette::kernelSetIntensity(uint16 fromColor, uint16 toColor, uint16 int } int16 GfxPalette::kernelFindColor(uint16 r, uint16 g, uint16 b) { - return matchColor(r, g, b) & 0xFF; + return matchColor(r, g, b) & SCI_PALETTE_MATCH_COLORMASK; } // Returns true, if palette got changed diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h index 347695deb8..500a45eccf 100644 --- a/engines/sci/graphics/palette.h +++ b/engines/sci/graphics/palette.h @@ -31,6 +31,10 @@ namespace Sci { class ResourceManager; class GfxScreen; +// Special flag implemented by us for optimization in palette merge +#define SCI_PALETTE_MATCH_PERFECT 0x8000 +#define SCI_PALETTE_MATCH_COLORMASK 0xFF + enum ColorRemappingType { kRemappingNone = 0, kRemappingByRange = 1, @@ -46,6 +50,7 @@ public: ~GfxPalette(); bool isMerging(); + bool isUsing16bitColorMatch(); void setDefault(); void createFromData(byte *data, int bytesLeft, Palette *paletteOut); @@ -124,6 +129,7 @@ private: bool _sysPaletteChanged; bool _useMerging; + bool _use16bitColorMatch; Common::Array<PalSchedule> _schedules; diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp index 434a490109..d7ef84dc1e 100644 --- a/engines/sci/graphics/picture.cpp +++ b/engines/sci/graphics/picture.cpp @@ -88,10 +88,13 @@ void GfxPicture::draw(int16 animationNr, bool mirroredFlag, bool addToFlag, int1 } void GfxPicture::reset() { + int16 startY = _ports->getPort()->top; + int16 startX = 0; int16 x, y; - for (y = _ports->getPort()->top; y < _screen->getHeight(); y++) { - for (x = 0; x < _screen->getWidth(); x++) { - _screen->putPixel(x, y, GFX_SCREEN_MASK_ALL, 255, 0, 0); + _screen->vectorAdjustCoordinate(&startX, &startY); + for (y = startY; y < _screen->getHeight(); y++) { + for (x = startX; x < _screen->getWidth(); x++) { + _screen->vectorPutPixel(x, y, GFX_SCREEN_MASK_ALL, 255, 0, 0); } } } @@ -246,7 +249,7 @@ void GfxPicture::drawCelData(byte *inbuffer, int size, int headerPos, int rlePos int16 y, lastY, x, leftX, rightX; int pixelCount; uint16 width, height; - + // if the picture is not an overlay and we are also not in EGA mode, use priority 0 if (!isEGA && !_addToFlag) priority = 0; @@ -362,7 +365,7 @@ void GfxPicture::drawCelData(byte *inbuffer, int size, int headerPos, int rlePos ptr = celBitmap; ptr += skipCelBitmapPixels; ptr += skipCelBitmapLines * width; - + if ((!isEGA) || (priority < 16)) { // VGA + EGA, EGA only checks priority, when given priority is below 16 if (!_mirroredFlag) { @@ -482,6 +485,8 @@ enum { PIC_OPX_VGA_PRIORITY_TABLE_EXPLICIT = 4 }; +//#define DEBUG_PICTURE_DRAW 1 + #ifdef DEBUG_PICTURE_DRAW const char *picOpcodeNames[] = { "Set color", @@ -589,6 +594,9 @@ void GfxPicture::drawVectorData(byte *data, int dataSize) { while (curPos < dataSize) { #ifdef DEBUG_PICTURE_DRAW debug("Picture op: %X (%s) at %d", data[curPos], picOpcodeNames[data[curPos] - 0xF0], curPos); + _screen->copyToScreen(); + g_system->updateScreen(); + g_system->delayMillis(400); #endif switch (pic_op = data[curPos++]) { case PIC_OP_SET_COLOR: @@ -934,17 +942,17 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by Common::Point p, p1; byte screenMask = _screen->getDrawingMask(color, priority, control); byte matchedMask, matchMask; - int16 w, e, a_set, b_set; bool isEGA = (_resMan->getViewType() == kViewEga); p.x = x + curPort->left; p.y = y + curPort->top; - stack.push(p); - byte searchColor = _screen->getVisual(p.x, p.y); - byte searchPriority = _screen->getPriority(p.x, p.y); - byte searchControl = _screen->getControl(p.x, p.y); + _screen->vectorAdjustCoordinate(&p.x, &p.y); + + byte searchColor = _screen->vectorGetVisual(p.x, p.y); + byte searchPriority = _screen->vectorGetPriority(p.x, p.y); + byte searchControl = _screen->vectorGetControl(p.x, p.y); if (isEGA) { // In EGA games a pixel in the framebuffer is only 4 bits. We store @@ -991,22 +999,31 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by } // hard borders for filling - int l = curPort->rect.left + curPort->left; - int t = curPort->rect.top + curPort->top; - int r = curPort->rect.right + curPort->left - 1; - int b = curPort->rect.bottom + curPort->top - 1; + int16 borderLeft = curPort->rect.left + curPort->left; + int16 borderTop = curPort->rect.top + curPort->top; + int16 borderRight = curPort->rect.right + curPort->left - 1; + int16 borderBottom = curPort->rect.bottom + curPort->top - 1; + int16 curToLeft, curToRight, a_set, b_set; + + // Translate coordinates, if required (needed for Macintosh 480x300) + _screen->vectorAdjustCoordinate(&borderLeft, &borderTop); + _screen->vectorAdjustCoordinate(&borderRight, &borderBottom); + //return; + + stack.push(p); + while (stack.size()) { p = stack.pop(); - if ((matchedMask = _screen->isFillMatch(p.x, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)) == 0) // already filled + if ((matchedMask = _screen->vectorIsFillMatch(p.x, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA)) == 0) // already filled continue; - _screen->putPixel(p.x, p.y, screenMask, color, priority, control); - w = p.x; - e = p.x; + _screen->vectorPutPixel(p.x, p.y, screenMask, color, priority, control); + curToLeft = p.x; + curToRight = p.x; // moving west and east pointers as long as there is a matching color to fill - while (w > l && (matchedMask = _screen->isFillMatch(w - 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA))) - _screen->putPixel(--w, p.y, screenMask, color, priority, control); - while (e < r && (matchedMask = _screen->isFillMatch(e + 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA))) - _screen->putPixel(++e, p.y, screenMask, color, priority, control); + while (curToLeft > borderLeft && (matchedMask = _screen->vectorIsFillMatch(curToLeft - 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA))) + _screen->vectorPutPixel(--curToLeft, p.y, screenMask, color, priority, control); + while (curToRight < borderRight && (matchedMask = _screen->vectorIsFillMatch(curToRight + 1, p.y, matchMask, searchColor, searchPriority, searchControl, isEGA))) + _screen->vectorPutPixel(++curToRight, p.y, screenMask, color, priority, control); #if 0 // debug code for floodfill _screen->copyToScreen(); @@ -1015,10 +1032,10 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by #endif // checking lines above and below for possible flood targets a_set = b_set = 0; - while (w <= e) { - if (p.y > t && (matchedMask = _screen->isFillMatch(w, p.y - 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line above + while (curToLeft <= curToRight) { + if (p.y > borderTop && (matchedMask = _screen->vectorIsFillMatch(curToLeft, p.y - 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line above if (a_set == 0) { - p1.x = w; + p1.x = curToLeft; p1.y = p.y - 1; stack.push(p1); a_set = 1; @@ -1026,16 +1043,16 @@ void GfxPicture::vectorFloodFill(int16 x, int16 y, byte color, byte priority, by } else a_set = 0; - if (p.y < b && (matchedMask = _screen->isFillMatch(w, p.y + 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line below + if (p.y < borderBottom && (matchedMask = _screen->vectorIsFillMatch(curToLeft, p.y + 1, matchMask, searchColor, searchPriority, searchControl, isEGA))) { // one line below if (b_set == 0) { - p1.x = w; + p1.x = curToLeft; p1.y = p.y + 1; stack.push(p1); b_set = 1; } } else b_set = 0; - w++; + curToLeft++; } } } @@ -1173,7 +1190,7 @@ void GfxPicture::vectorPatternBox(Common::Rect box, byte color, byte prio, byte for (y = box.top; y < box.bottom; y++) { for (x = box.left; x < box.right; x++) { - _screen->putPixel(x, y, flag, color, prio, control); + _screen->vectorPutPixel(x, y, flag, color, prio, control); } } } @@ -1186,7 +1203,7 @@ void GfxPicture::vectorPatternTexturedBox(Common::Rect box, byte color, byte pri for (y = box.top; y < box.bottom; y++) { for (x = box.left; x < box.right; x++) { if (*textureData) { - _screen->putPixel(x, y, flag, color, prio, control); + _screen->vectorPutPixel(x, y, flag, color, prio, control); } textureData++; } @@ -1203,7 +1220,7 @@ void GfxPicture::vectorPatternCircle(Common::Rect box, byte size, byte color, by for (y = box.top; y < box.bottom; y++) { for (x = box.left; x < box.right; x++) { if (bitmap & 1) { - _screen->putPixel(x, y, flag, color, prio, control); + _screen->vectorPutPixel(x, y, flag, color, prio, control); } bitNo++; if (bitNo == 8) { @@ -1222,12 +1239,12 @@ void GfxPicture::vectorPatternTexturedCircle(Common::Rect box, byte size, byte c byte bitNo = 0; const bool *textureData = &vectorPatternTextures[vectorPatternTextureOffset[texture]]; int y, x; - + for (y = box.top; y < box.bottom; y++) { for (x = box.left; x < box.right; x++) { if (bitmap & 1) { if (*textureData) { - _screen->putPixel(x, y, flag, color, prio, control); + _screen->vectorPutPixel(x, y, flag, color, prio, control); } textureData++; } @@ -1252,7 +1269,10 @@ void GfxPicture::vectorPattern(int16 x, int16 y, byte color, byte priority, byte rect.top = y; rect.left = x; rect.setHeight((size*2)+1); rect.setWidth((size*2)+2); _ports->offsetRect(rect); - rect.clip(_screen->getWidth(), _screen->getHeight()); + rect.clip(_screen->getScriptWidth(), _screen->getScriptHeight()); + + _screen->vectorAdjustCoordinate(&rect.left, &rect.top); + _screen->vectorAdjustCoordinate(&rect.right, &rect.bottom); if (code & SCI_PATTERN_CODE_RECTANGLE) { // Rectangle diff --git a/engines/sci/graphics/portrait.cpp b/engines/sci/graphics/portrait.cpp index 488450485d..3311f47022 100644 --- a/engines/sci/graphics/portrait.cpp +++ b/engines/sci/graphics/portrait.cpp @@ -57,13 +57,39 @@ void Portrait::init() { // 4 bytes paletteSize (base 1) // -> 17 bytes // paletteSize bytes paletteData - // 14 bytes bitmap header - // -> 4 bytes unknown - // -> 2 bytes height - // -> 2 bytes width - // -> 6 bytes unknown - // height * width bitmap data - // another animation count times bitmap header and data + // + // bitmap-data follows, total of [animation count] + // 14 bytes bitmap header + // -> 4 bytes unknown + // -> 2 bytes height + // -> 2 bytes width + // -> 6 bytes unknown + // height * width bitmap data + // + // 4 bytes offset table size (may be larger than the actual known entries?!) + // 14 bytes all zeroes (dummy entry?!) + // + // 14 bytes for each entry + // -> 2 bytes displace X + // -> 2 bytes displace Y + // -> 2 bytes height (again) + // -> 2 bytes width (again) + // -> 6 bytes unknown (normally 01 00 00 00 00 00 for delta bitmaps, 00 00 00 00 00 00 for first bitmap) + // random data may be used as filler + // + // 4 bytes lip sync id table size (is [lip sync id count] * 4, should be 0x2E0 for all actors) + // 4 bytes per lip sync id + // -> 1 byte length of ID + // -> 3 bytes actual ID + // + // 4 bytes lip sync id data table size (seems to be the same for all actors, always 0x220 in size) + // 1 byte animation number or 0xFF as terminator + // 1 byte delay, if last byte was not terminator + // one array for every lip sync id + // + // 4 bytes appended, seem to be random + // 9E11120E for alex + // 9E9E9E9E for vizier int32 fileSize = 0; Common::SeekableReadStream *file = SearchMan.createReadStreamForMember("actors/" + _resourceName + ".bin"); @@ -134,34 +160,34 @@ void Portrait::init() { // raw lip-sync ID table follows uint32 lipSyncIDTableSize; - + lipSyncIDTableSize = READ_LE_UINT32(data); data += 4; assert( lipSyncIDTableSize == (_lipSyncIDCount * 4) ); _lipSyncIDTable = data; data += lipSyncIDTableSize; - + // raw lip-sync frame table follows uint32 lipSyncDataTableSize; uint32 lipSyncDataTableLastOffset; byte lipSyncData; uint16 lipSyncDataNr; uint16 lipSyncCurOffset; - + lipSyncDataTableSize = READ_LE_UINT32(data); data += 4; assert( lipSyncDataTableSize == 0x220 ); // always this size, just a safety-check - + _lipSyncData = data; lipSyncDataTableLastOffset = lipSyncDataTableSize - 1; _lipSyncDataOffsetTable = new uint16[ _lipSyncIDCount ]; - + lipSyncDataNr = 0; lipSyncCurOffset = 0; while ( (lipSyncCurOffset < lipSyncDataTableSize) && (lipSyncDataNr < _lipSyncIDCount) ) { // We are currently at the start of ID-frame data _lipSyncDataOffsetTable[lipSyncDataNr] = lipSyncCurOffset; - + // Look for end of ID-frame data lipSyncData = *data++; lipSyncCurOffset++; while ( (lipSyncData != 0xFF) && (lipSyncCurOffset < lipSyncDataTableLastOffset) ) { @@ -195,15 +221,16 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint Resource *syncResource = _resMan->findResource(syncResourceId, true); uint syncOffset = 0; #endif - + #ifdef DEBUG_PORTRAIT // prints out the current lip sync ASCII data char debugPrint[4000]; if (raveResource->size < 4000) { memcpy(debugPrint, raveResource->data, raveResource->size); debugPrint[raveResource->size] = 0; // set terminating NUL + debug("kPortrait: using actor %s", _resourceName.c_str()); debug("kPortrait (noun %d, verb %d, cond %d, seq %d)", noun, verb, cond, seq); - debug("kPortrait: %s", debugPrint); + debug("kPortrait: rave data is '%s'", debugPrint); } #endif @@ -246,7 +273,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint warning("kPortrait: no rave resource %d %X", resourceId, audioNumber); return; } - + // Do animation depending on rave resource till audio is done playing int16 raveTicks; uint16 raveID; @@ -264,7 +291,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint raveTicks = raveGetTicks(raveResource, &raveOffset); if (raveTicks < 0) break; - + // get lipSyncID raveID = raveGetID(raveResource, &raveOffset); if (raveID) { @@ -272,7 +299,15 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint } else { raveLipSyncData = NULL; } - + +#ifdef DEBUG_PORTRAIT + if (raveID & 0x0ff) { + debug("kPortrait: rave '%c%c' after %d ticks", raveID >> 8, raveID & 0x0ff, raveTicks); + } else if (raveID) { + debug("kPortrait: rave '%c' after %d ticks", raveID >> 8, raveTicks); + } +#endif + timerPosition += raveTicks; // Wait till syncTime passed, then show specific animation bitmap @@ -287,7 +322,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint curPosition = _audio->getAudioPosition(); } while ((curPosition != -1) && (curPosition < timerPosition) && (!userAbort)); } - + if (raveLipSyncData) { // lip sync data is // Tick:Byte, Bitmap-Nr:BYTE @@ -295,6 +330,8 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint timerPositionWithin = timerPosition; raveLipSyncTicks = *raveLipSyncData++; while ( (raveLipSyncData < _lipSyncDataOffsetTableEnd) && (raveLipSyncTicks != 0xFF) ) { + if (raveLipSyncTicks) + raveLipSyncTicks--; // 1 -> wait 0 ticks, 2 -> wait 1 tick, etc. timerPositionWithin += raveLipSyncTicks; do { @@ -308,7 +345,14 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint } while ((curPosition != -1) && (curPosition < timerPositionWithin) && (!userAbort)); raveLipSyncBitmapNr = *raveLipSyncData++; - +#ifdef DEBUG_PORTRAIT + if (!raveLipSyncTicks) { + debug("kPortrait: showing frame %d", raveLipSyncBitmapNr); + } else { + debug("kPortrait: showing frame %d after %d ticks", raveLipSyncBitmapNr, raveLipSyncTicks); + } +#endif + // bitmap nr within sync data is base 1, we need base 0 raveLipSyncBitmapNr--; @@ -319,7 +363,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint } else { warning("kPortrait: rave lip sync data tried to draw non-existent bitmap %d", raveLipSyncBitmapNr); } - + raveLipSyncTicks = *raveLipSyncData++; } } @@ -372,7 +416,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint } } #endif - + // Reset the portrait bitmap to "closed mouth" state (rave.dll seems to do the same) drawBitmap(0); bitsShow(); @@ -393,10 +437,10 @@ int16 Portrait::raveGetTicks(Resource *resource, uint *offset) { byte *curData = resource->data + curOffset; byte curByte; uint16 curValue = 0; - + if (curOffset >= resource->size) return -1; - + while (curOffset < resource->size) { curByte = *curData++; curOffset++; if ( curByte == ' ' ) @@ -418,7 +462,7 @@ uint16 Portrait::raveGetID(Resource *resource, uint *offset) { byte *curData = resource->data + curOffset; byte curByte = 0; uint16 curValue = 0; - + while (curOffset < resource->size) { curByte = *curData++; curOffset++; if ( curByte == ' ' ) @@ -429,7 +473,7 @@ uint16 Portrait::raveGetID(Resource *resource, uint *offset) { curValue |= curByte; } } - + *offset = curOffset; return curValue; } @@ -440,17 +484,17 @@ byte *Portrait::raveGetLipSyncData(uint16 raveID) { byte *lipSyncIDPtr = _lipSyncIDTable; byte lipSyncIDByte1, lipSyncIDByte2; uint16 lipSyncID; - + lipSyncIDPtr++; // skip over first byte while (lipSyncIDNr < _lipSyncIDCount) { lipSyncIDByte1 = *lipSyncIDPtr++; lipSyncIDByte2 = *lipSyncIDPtr++; lipSyncID = ( lipSyncIDByte1 << 8 ) | lipSyncIDByte2; - + if ( lipSyncID == raveID ) { return _lipSyncData + _lipSyncDataOffsetTable[lipSyncIDNr]; } - + lipSyncIDNr++; lipSyncIDPtr += 2; // ID is every 4 bytes } diff --git a/engines/sci/graphics/portrait.h b/engines/sci/graphics/portrait.h index 877b253bcf..e0888daa86 100644 --- a/engines/sci/graphics/portrait.h +++ b/engines/sci/graphics/portrait.h @@ -72,7 +72,7 @@ private: Common::String _resourceName; byte *_fileData; - + uint32 _lipSyncIDCount; byte *_lipSyncIDTable; diff --git a/engines/sci/graphics/ports.cpp b/engines/sci/graphics/ports.cpp index 56c63a7b12..bcc991081e 100644 --- a/engines/sci/graphics/ports.cpp +++ b/engines/sci/graphics/ports.cpp @@ -63,10 +63,10 @@ void GfxPorts::init(bool usesOldGfxFunctions, GfxPaint16 *paint16, GfxText16 *te openPort(_menuPort); setPort(_menuPort); _text16->SetFont(0); - _menuPort->rect = Common::Rect(0, 0, _screen->getWidth(), _screen->getHeight()); - _menuBarRect = Common::Rect(0, 0, _screen->getWidth(), 9); - _menuRect = Common::Rect(0, 0, _screen->getWidth(), 10); - _menuLine = Common::Rect(0, 9, _screen->getWidth(), 10); + _menuPort->rect = Common::Rect(0, 0, _screen->getScriptWidth(), _screen->getScriptHeight()); + _menuBarRect = Common::Rect(0, 0, _screen->getScriptWidth(), 9); + _menuRect = Common::Rect(0, 0, _screen->getScriptWidth(), 10); + _menuLine = Common::Rect(0, 9, _screen->getScriptWidth(), 10); _wmgrPort = new Port(1); _windowsById.resize(2); @@ -122,13 +122,13 @@ void GfxPorts::init(bool usesOldGfxFunctions, GfxPaint16 *paint16, GfxText16 *te } else { _wmgrPort->rect.bottom = _screen->getHeight(); } - _wmgrPort->rect.right = _screen->getWidth(); + _wmgrPort->rect.right = _screen->getScriptWidth(); _wmgrPort->rect.moveTo(0, 0); _wmgrPort->curTop = 0; _wmgrPort->curLeft = 0; _windowList.push_front(_wmgrPort); - _picWind = addWindow(Common::Rect(0, offTop, _screen->getWidth(), _screen->getHeight()), 0, 0, SCI_WINDOWMGR_STYLE_TRANSPARENT | SCI_WINDOWMGR_STYLE_NOFRAME, 0, true); + _picWind = addWindow(Common::Rect(0, offTop, _screen->getScriptWidth(), _screen->getScriptHeight()), 0, 0, SCI_WINDOWMGR_STYLE_TRANSPARENT | SCI_WINDOWMGR_STYLE_NOFRAME, 0, true); // For SCI0 games till kq4 (.502 - not including) we set _picWind top to offTop instead // Because of the menu/status bar if (_usesOldGfxFunctions) @@ -321,13 +321,13 @@ Window *GfxPorts::addWindow(const Common::Rect &dims, const Common::Rect *restor // their interpreter even in the newer VGA games. r.left = r.left & 0xFFFE; - if (r.width() > _screen->getWidth()) { + if (r.width() > _screen->getScriptWidth()) { // We get invalid dimensions at least at the end of sq3 (script bug!). // Same happens very often in lsl5, sierra sci didnt fix it but it looked awful. // Also happens frequently in the demo of GK1. warning("Fixing too large window, left: %d, right: %d", dims.left, dims.right); r.left = 0; - r.right = _screen->getWidth() - 1; + r.right = _screen->getScriptWidth() - 1; if ((style != _styleUser) && !(style & SCI_WINDOWMGR_STYLE_NOFRAME)) r.right--; } diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index c5c94d7991..2f95bf7751 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -37,7 +37,15 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { // Scale the screen, if needed _upscaledHires = GFX_SCREEN_UPSCALED_DISABLED; - + + // we default to scripts running at 320x200 + _scriptWidth = 320; + _scriptHeight = 200; + _width = 0; + _height = 0; + _displayWidth = 0; + _displayHeight = 0; + // King's Quest 6 and Gabriel Knight 1 have hires content, gk1/cd was able // to provide that under DOS as well, but as gk1/floppy does support // upscaled hires scriptswise, but doesn't actually have the hires content @@ -50,10 +58,33 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { _upscaledHires = GFX_SCREEN_UPSCALED_640x480; #endif } + + // Japanese versions of games use hi-res font on upscaled version of the game. + if ((g_sci->getLanguage() == Common::JA_JPN) && (getSciVersion() <= SCI_VERSION_1_1)) + _upscaledHires = GFX_SCREEN_UPSCALED_640x400; + // Macintosh SCI0 games used 480x300, while the scripts were running at 320x200 if (g_sci->getPlatform() == Common::kPlatformMacintosh) { - if (getSciVersion() <= SCI_VERSION_01) + if (getSciVersion() <= SCI_VERSION_01) { _upscaledHires = GFX_SCREEN_UPSCALED_480x300; + _width = 480; + _height = 300; // regular visual, priority and control map are 480x300 (this is different than other upscaled SCI games) + } + + // Some Mac SCI1/1.1 games only take up 190 rows and do not + // have the menu bar. + // TODO: Verify that LSL1 and LSL5 use height 190 + switch (g_sci->getGameId()) { + case GID_FREDDYPHARKAS: + case GID_KQ5: + case GID_KQ6: + case GID_LSL1: + case GID_LSL5: + case GID_SQ1: + _width = 190; + default: + break; + } } #ifdef ENABLE_SCI32 @@ -65,76 +96,77 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { #endif if (_resMan->detectHires()) { - _width = 640; - _pitch = 640; - _height = 480; - } else { - _width = 320; - _pitch = 320; - _height = getLowResScreenHeight(); + _scriptWidth = 640; + _scriptHeight = 480; } #ifdef ENABLE_SCI32 - // Phantasmagoria 1 sets a window area of 630x450 + // Phantasmagoria 1 effectively outputs 630x450 + // Coordinate translation has to use this resolution as well if (g_sci->getGameId() == GID_PHANTASMAGORIA) { _width = 630; _height = 450; } #endif - // Japanese versions of games use hi-res font on upscaled version of the game. - if ((g_sci->getLanguage() == Common::JA_JPN) && (getSciVersion() <= SCI_VERSION_1_1)) - _upscaledHires = GFX_SCREEN_UPSCALED_640x400; + // if not yet set, set those to script-width/height + if (!_width) + _width = _scriptWidth; + if (!_height) + _height = _scriptHeight; - _pixels = _pitch * _height; + _pixels = _width * _height; switch (_upscaledHires) { case GFX_SCREEN_UPSCALED_480x300: // Space Quest 3, Hoyle 1+2 on MAC use this one - // TODO: Sierra's upscaling worked differently. We need to figure out the exact algo _displayWidth = 480; _displayHeight = 300; - for (int i = 0; i <= _height; i++) + for (int i = 0; i <= _scriptHeight; i++) _upscaledHeightMapping[i] = (i * 3) >> 1; - for (int i = 0; i <= _width; i++) + for (int i = 0; i <= _scriptWidth; i++) _upscaledWidthMapping[i] = (i * 3) >> 1; break; case GFX_SCREEN_UPSCALED_640x400: // Police Quest 2 and Quest For Glory on PC9801 (Japanese) _displayWidth = 640; _displayHeight = 400; - for (int i = 0; i <= _height; i++) + for (int i = 0; i <= _scriptHeight; i++) _upscaledHeightMapping[i] = i * 2; - for (int i = 0; i <= _width; i++) + for (int i = 0; i <= _scriptWidth; i++) _upscaledWidthMapping[i] = i * 2; break; case GFX_SCREEN_UPSCALED_640x440: // used by King's Quest 6 on Windows _displayWidth = 640; _displayHeight = 440; - for (int i = 0; i <= _height; i++) + for (int i = 0; i <= _scriptHeight; i++) _upscaledHeightMapping[i] = (i * 11) / 5; - for (int i = 0; i <= _width; i++) + for (int i = 0; i <= _scriptWidth; i++) _upscaledWidthMapping[i] = i * 2; break; case GFX_SCREEN_UPSCALED_640x480: // Gabriel Knight 1 (VESA, Mac) _displayWidth = 640; _displayHeight = 480; - for (int i = 0; i <= _height; i++) + for (int i = 0; i <= _scriptHeight; i++) _upscaledHeightMapping[i] = (i * 12) / 5; - for (int i = 0; i <= _width; i++) + for (int i = 0; i <= _scriptWidth; i++) _upscaledWidthMapping[i] = i * 2; break; default: - _displayWidth = _pitch; - _displayHeight = _height; + if (!_displayWidth) + _displayWidth = _width; + if (!_displayHeight) + _displayHeight = _height; memset(&_upscaledHeightMapping, 0, sizeof(_upscaledHeightMapping) ); memset(&_upscaledWidthMapping, 0, sizeof(_upscaledWidthMapping) ); break; } _displayPixels = _displayWidth * _displayHeight; + + // Allocate visual, priority, control and display screen _visualScreen = (byte *)calloc(_pixels, 1); _priorityScreen = (byte *)calloc(_pixels, 1); _controlScreen = (byte *)calloc(_pixels, 1); @@ -179,6 +211,36 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { error("Unknown SCI1.1 Mac game"); } else initGraphics(_displayWidth, _displayHeight, _displayWidth > 320); + + // Initialize code pointers + _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinateNOP; + _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinatesNOP; + _vectorIsFillMatchPtr = &GfxScreen::vectorIsFillMatchNormal; + _vectorPutPixelPtr = &GfxScreen::putPixelNormal; + _vectorPutLinePixelPtr = &GfxScreen::putPixel; + _vectorGetPixelPtr = &GfxScreen::getPixelNormal; + _putPixelPtr = &GfxScreen::putPixelNormal; + _getPixelPtr = &GfxScreen::getPixelNormal; + + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_480x300: + _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinate480x300Mac; + _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinates480x300Mac; + // vectorPutPixel -> we already adjust coordinates for vector code, that's why we can set pixels directly + // vectorGetPixel -> see vectorPutPixel + _vectorPutLinePixelPtr = &GfxScreen::vectorPutLinePixel480x300Mac; + _putPixelPtr = &GfxScreen::putPixelAllUpscaled; + _getPixelPtr = &GfxScreen::getPixelUpscaled; + break; + case GFX_SCREEN_UPSCALED_640x400: + case GFX_SCREEN_UPSCALED_640x440: + case GFX_SCREEN_UPSCALED_640x480: + _vectorPutPixelPtr = &GfxScreen::putPixelDisplayUpscaled; + _putPixelPtr = &GfxScreen::putPixelDisplayUpscaled; + break; + case GFX_SCREEN_UPSCALED_DISABLED: + break; + } } GfxScreen::~GfxScreen() { @@ -232,7 +294,7 @@ void GfxScreen::copyRectToScreen(const Common::Rect &rect, int16 x, int16 y) { } else { int rectHeight = _upscaledHeightMapping[rect.bottom] - _upscaledHeightMapping[rect.top]; int rectWidth = _upscaledWidthMapping[rect.right] - _upscaledWidthMapping[rect.left]; - + g_system->copyRectToScreen(_activeScreen + _upscaledHeightMapping[rect.top] * _displayWidth + _upscaledWidthMapping[rect.left], _displayWidth, _upscaledWidthMapping[x], _upscaledHeightMapping[y], rectWidth, rectHeight); } } @@ -248,43 +310,162 @@ byte GfxScreen::getDrawingMask(byte color, byte prio, byte control) { return flag; } -void GfxScreen::putPixel(int x, int y, byte drawMask, byte color, byte priority, byte control) { - int offset = y * _pitch + x; +void GfxScreen::vectorAdjustCoordinateNOP(int16 *x, int16 *y) { +} - if (drawMask & GFX_SCREEN_MASK_VISUAL) { - _visualScreen[offset] = color; - if (!_upscaledHires) { - _displayScreen[offset] = color; +void GfxScreen::vectorAdjustCoordinate480x300Mac(int16 *x, int16 *y) { + *x = _upscaledWidthMapping[*x]; + *y = _upscaledHeightMapping[*y]; +} + +void GfxScreen::vectorAdjustLineCoordinatesNOP(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) { +} + +void GfxScreen::vectorAdjustLineCoordinates480x300Mac(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) { + int16 displayLeft = _upscaledWidthMapping[*left]; + int16 displayRight = _upscaledWidthMapping[*right]; + int16 displayTop = _upscaledHeightMapping[*top]; + int16 displayBottom = _upscaledHeightMapping[*bottom]; + + if (displayLeft < displayRight) { + // one more pixel to the left, one more pixel to the right + if (displayLeft > 0) + vectorPutLinePixel(displayLeft - 1, displayTop, drawMask, color, priority, control); + vectorPutLinePixel(displayRight + 1, displayBottom, drawMask, color, priority, control); + } else if (displayLeft > displayRight) { + if (displayRight > 0) + vectorPutLinePixel(displayRight - 1, displayBottom, drawMask, color, priority, control); + vectorPutLinePixel(displayLeft + 1, displayTop, drawMask, color, priority, control); + } + *left = displayLeft; + *top = displayTop; + *right = displayRight; + *bottom = displayBottom; +} + +byte GfxScreen::vectorIsFillMatchNormal(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA) { + int offset = y * _width + x; + byte match = 0; + + if (screenMask & GFX_SCREEN_MASK_VISUAL) { + if (!isEGA) { + if (*(_visualScreen + offset) == checkForColor) + match |= GFX_SCREEN_MASK_VISUAL; } else { - putScaledPixelOnDisplay(x, y, color); + // In EGA games a pixel in the framebuffer is only 4 bits. We store + // a full byte per pixel to allow undithering, but when comparing + // pixels for flood-fill purposes, we should only compare the + // visible color of a pixel. + + byte EGAcolor = *(_visualScreen + offset); + if ((x ^ y) & 1) + EGAcolor = (EGAcolor ^ (EGAcolor >> 4)) & 0x0F; + else + EGAcolor = EGAcolor & 0x0F; + if (EGAcolor == checkForColor) + match |= GFX_SCREEN_MASK_VISUAL; } } + if ((screenMask & GFX_SCREEN_MASK_PRIORITY) && *(_priorityScreen + offset) == checkForPriority) + match |= GFX_SCREEN_MASK_PRIORITY; + if ((screenMask & GFX_SCREEN_MASK_CONTROL) && *(_controlScreen + offset) == checkForControl) + match |= GFX_SCREEN_MASK_CONTROL; + return match; +} + +// Special 480x300 Mac putPixel for vector line drawing, also draws an additional pixel below the actual one +void GfxScreen::vectorPutLinePixel480x300Mac(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + int offset = y * _width + x; + + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + _visualScreen[offset] = color; + _visualScreen[offset + _width] = color; + _displayScreen[offset] = color; + // also set pixel below actual pixel + _displayScreen[offset + _displayWidth] = color; + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) { + _priorityScreen[offset] = priority; + _priorityScreen[offset + _width] = priority; + } + if (drawMask & GFX_SCREEN_MASK_CONTROL) { + _controlScreen[offset] = control; + _controlScreen[offset + _width] = control; + } +} + +// Directly sets a pixel on various screens, display is not upscaled +void GfxScreen::putPixelNormal(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + int offset = y * _width + x; + + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + _visualScreen[offset] = color; + _displayScreen[offset] = color; + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) + _priorityScreen[offset] = priority; + if (drawMask & GFX_SCREEN_MASK_CONTROL) + _controlScreen[offset] = control; +} + +// Directly sets a pixel on various screens, display IS upscaled +void GfxScreen::putPixelDisplayUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + int offset = y * _width + x; + + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + _visualScreen[offset] = color; + putScaledPixelOnScreen(_displayScreen, x, y, color); + } if (drawMask & GFX_SCREEN_MASK_PRIORITY) _priorityScreen[offset] = priority; if (drawMask & GFX_SCREEN_MASK_CONTROL) _controlScreen[offset] = control; } +// Directly sets a pixel on various screens, ALL screens ARE upscaled +void GfxScreen::putPixelAllUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + putScaledPixelOnScreen(_visualScreen, x, y, color); + putScaledPixelOnScreen(_displayScreen, x, y, color); + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) + putScaledPixelOnScreen(_priorityScreen, x, y, priority); + if (drawMask & GFX_SCREEN_MASK_CONTROL) + putScaledPixelOnScreen(_controlScreen, x, y, control); +} + /** * This is used to put font pixels onto the screen - we adjust differently, so that we won't * do triple pixel lines in any case on upscaled hires. That way the font will not get distorted * Sierra SCI didn't do this */ -void GfxScreen::putFontPixel(int startingY, int x, int y, byte color) { - int actualY = startingY + y; +void GfxScreen::putFontPixel(int16 startingY, int16 x, int16 y, byte color) { + int16 actualY = startingY + y; if (_fontIsUpscaled) { // Do not scale ourselves, but put it on the display directly putPixelOnDisplay(x, actualY, color); } else { - int offset = actualY * _pitch + x; + int offset = actualY * _width + x; _visualScreen[offset] = color; switch (_upscaledHires) { case GFX_SCREEN_UPSCALED_DISABLED: _displayScreen[offset] = color; break; + case GFX_SCREEN_UPSCALED_640x400: + case GFX_SCREEN_UPSCALED_640x440: + case GFX_SCREEN_UPSCALED_640x480: { + // to 1-> 4 pixels upscaling for all of those, so that fonts won't look weird + int displayOffset = (_upscaledHeightMapping[startingY] + y * 2) * _displayWidth + x * 2; + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + displayOffset += _displayWidth; + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + break; + } default: - putScaledPixelOnDisplay(x, actualY, color); + putScaledPixelOnScreen(_displayScreen, x, actualY, color); break; } } @@ -295,12 +476,15 @@ void GfxScreen::putFontPixel(int startingY, int x, int y, byte color) { * only used on upscaled-Hires games where hires content needs to get drawn ONTO * the upscaled display screen (like japanese fonts, hires portraits, etc.). */ -void GfxScreen::putPixelOnDisplay(int x, int y, byte color) { +void GfxScreen::putPixelOnDisplay(int16 x, int16 y, byte color) { int offset = y * _displayWidth + x; _displayScreen[offset] = color; } -void GfxScreen::putScaledPixelOnDisplay(int x, int y, byte color) { +//void GfxScreen::putScaledPixelOnDisplay(int16 x, int16 y, byte color) { +//} + +void GfxScreen::putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte data) { int displayOffset = _upscaledHeightMapping[y] * _displayWidth + _upscaledWidthMapping[x]; int heightOffsetBreak = (_upscaledHeightMapping[y + 1] - _upscaledHeightMapping[y]) * _displayWidth; int heightOffset = 0; @@ -308,7 +492,7 @@ void GfxScreen::putScaledPixelOnDisplay(int x, int y, byte color) { do { int widthOffset = 0; do { - _displayScreen[displayOffset + heightOffset + widthOffset] = color; + screen[displayOffset + heightOffset + widthOffset] = data; widthOffset++; } while (widthOffset != widthOffsetBreak); heightOffset += _displayWidth; @@ -329,16 +513,18 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte int16 top = CLIP<int16>(startPoint.y, 0, maxHeight); int16 right = CLIP<int16>(endPoint.x, 0, maxWidth); int16 bottom = CLIP<int16>(endPoint.y, 0, maxHeight); - + //set_drawing_flag byte drawMask = getDrawingMask(color, priority, control); + vectorAdjustLineCoordinates(&left, &top, &right, &bottom, drawMask, color, priority, control); + // horizontal line if (top == bottom) { if (right < left) SWAP(right, left); for (int i = left; i <= right; i++) - putPixel(i, top, drawMask, color, priority, control); + vectorPutLinePixel(i, top, drawMask, color, priority, control); return; } // vertical line @@ -346,20 +532,20 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte if (top > bottom) SWAP(top, bottom); for (int i = top; i <= bottom; i++) - putPixel(left, i, drawMask, color, priority, control); + vectorPutLinePixel(left, i, drawMask, color, priority, control); return; } // sloped line - draw with Bresenham algorithm - int dy = bottom - top; - int dx = right - left; - int stepy = dy < 0 ? -1 : 1; - int stepx = dx < 0 ? -1 : 1; + int16 dy = bottom - top; + int16 dx = right - left; + int16 stepy = dy < 0 ? -1 : 1; + int16 stepx = dx < 0 ? -1 : 1; dy = ABS(dy) << 1; dx = ABS(dx) << 1; // setting the 1st and last pixel - putPixel(left, top, drawMask, color, priority, control); - putPixel(right, bottom, drawMask, color, priority, control); + vectorPutLinePixel(left, top, drawMask, color, priority, control); + vectorPutLinePixel(right, bottom, drawMask, color, priority, control); // drawing the line if (dx > dy) { // going horizontal int fraction = dy - (dx >> 1); @@ -370,7 +556,7 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte } left += stepx; fraction += dy; - putPixel(left, top, drawMask, color, priority, control); + vectorPutLinePixel(left, top, drawMask, color, priority, control); } } else { // going vertical int fraction = dx - (dy >> 1); @@ -381,7 +567,7 @@ void GfxScreen::drawLine(Common::Point startPoint, Common::Point endPoint, byte } top += stepy; fraction += dx; - putPixel(left, top, drawMask, color, priority, control); + vectorPutLinePixel(left, top, drawMask, color, priority, control); } } } @@ -394,46 +580,14 @@ void GfxScreen::putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, u commonFont->drawChar(displayPtr, chr, _displayWidth, 1, color, 0, -1, -1); } -byte GfxScreen::getVisual(int x, int y) { - return _visualScreen[y * _pitch + x]; -} - -byte GfxScreen::getPriority(int x, int y) { - return _priorityScreen[y * _pitch + x]; -} - -byte GfxScreen::getControl(int x, int y) { - return _controlScreen[y * _pitch + x]; +byte GfxScreen::getPixelNormal(byte *screen, int16 x, int16 y) { + return screen[y * _width + x]; } -byte GfxScreen::isFillMatch(int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) { - int offset = y * _pitch + x; - byte match = 0; - - if (screenMask & GFX_SCREEN_MASK_VISUAL) { - if (!isEGA) { - if (*(_visualScreen + offset) == t_color) - match |= GFX_SCREEN_MASK_VISUAL; - } else { - // In EGA games a pixel in the framebuffer is only 4 bits. We store - // a full byte per pixel to allow undithering, but when comparing - // pixels for flood-fill purposes, we should only compare the - // visible color of a pixel. - - byte c = *(_visualScreen + offset); - if ((x ^ y) & 1) - c = (c ^ (c >> 4)) & 0x0F; - else - c = c & 0x0F; - if (c == t_color) - match |= GFX_SCREEN_MASK_VISUAL; - } - } - if ((screenMask & GFX_SCREEN_MASK_PRIORITY) && *(_priorityScreen + offset) == t_pri) - match |= GFX_SCREEN_MASK_PRIORITY; - if ((screenMask & GFX_SCREEN_MASK_CONTROL) && *(_controlScreen + offset) == t_con) - match |= GFX_SCREEN_MASK_CONTROL; - return match; +byte GfxScreen::getPixelUpscaled(byte *screen, int16 x, int16 y) { + int16 mappedX = _upscaledWidthMapping[x]; + int16 mappedY = _upscaledHeightMapping[y]; + return screen[mappedY * _width + mappedX]; } int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) { @@ -469,14 +623,14 @@ void GfxScreen::bitsSave(Common::Rect rect, byte mask, byte *memoryPtr) { memcpy(memoryPtr, (void *)&mask, sizeof(mask)); memoryPtr += sizeof(mask); if (mask & GFX_SCREEN_MASK_VISUAL) { - bitsSaveScreen(rect, _visualScreen, _pitch, memoryPtr); + bitsSaveScreen(rect, _visualScreen, _width, memoryPtr); bitsSaveDisplayScreen(rect, memoryPtr); } if (mask & GFX_SCREEN_MASK_PRIORITY) { - bitsSaveScreen(rect, _priorityScreen, _pitch, memoryPtr); + bitsSaveScreen(rect, _priorityScreen, _width, memoryPtr); } if (mask & GFX_SCREEN_MASK_CONTROL) { - bitsSaveScreen(rect, _controlScreen, _pitch, memoryPtr); + bitsSaveScreen(rect, _controlScreen, _width, memoryPtr); } if (mask & GFX_SCREEN_MASK_DISPLAY) { if (!_upscaledHires) @@ -530,14 +684,14 @@ void GfxScreen::bitsRestore(byte *memoryPtr) { memcpy((void *)&mask, memoryPtr, sizeof(mask)); memoryPtr += sizeof(mask); if (mask & GFX_SCREEN_MASK_VISUAL) { - bitsRestoreScreen(rect, memoryPtr, _visualScreen, _pitch); + bitsRestoreScreen(rect, memoryPtr, _visualScreen, _width); bitsRestoreDisplayScreen(rect, memoryPtr); } if (mask & GFX_SCREEN_MASK_PRIORITY) { - bitsRestoreScreen(rect, memoryPtr, _priorityScreen, _pitch); + bitsRestoreScreen(rect, memoryPtr, _priorityScreen, _width); } if (mask & GFX_SCREEN_MASK_CONTROL) { - bitsRestoreScreen(rect, memoryPtr, _controlScreen, _pitch); + bitsRestoreScreen(rect, memoryPtr, _controlScreen, _width); } if (mask & GFX_SCREEN_MASK_DISPLAY) { if (!_upscaledHires) @@ -612,21 +766,22 @@ void GfxScreen::dither(bool addToFlag) { byte color, ditheredColor; byte *visualPtr = _visualScreen; byte *displayPtr = _displayScreen; - + if (!_unditheringEnabled) { // Do dithering on visual and display-screen for (y = 0; y < _height; y++) { - for (x = 0; x < _pitch; x++) { + for (x = 0; x < _width; x++) { color = *visualPtr; if (color & 0xF0) { color ^= color << 4; color = ((x^y) & 1) ? color >> 4 : color & 0x0F; switch (_upscaledHires) { case GFX_SCREEN_UPSCALED_DISABLED: + case GFX_SCREEN_UPSCALED_480x300: *displayPtr = color; break; default: - putScaledPixelOnDisplay(x, y, color); + putScaledPixelOnScreen(_displayScreen, x, y, color); break; } *visualPtr = color; @@ -639,7 +794,7 @@ void GfxScreen::dither(bool addToFlag) { memset(&_ditheredPicColors, 0, sizeof(_ditheredPicColors)); // Do dithering on visual screen and put decoded but undithered byte onto display-screen for (y = 0; y < _height; y++) { - for (x = 0; x < _pitch; x++) { + for (x = 0; x < _width; x++) { color = *visualPtr; if (color & 0xF0) { color ^= color << 4; @@ -654,10 +809,11 @@ void GfxScreen::dither(bool addToFlag) { } switch (_upscaledHires) { case GFX_SCREEN_UPSCALED_DISABLED: + case GFX_SCREEN_UPSCALED_480x300: *displayPtr = ditheredColor; break; default: - putScaledPixelOnDisplay(x, y, ditheredColor); + putScaledPixelOnScreen(_displayScreen, x, y, ditheredColor); break; } color = ((x^y) & 1) ? color >> 4 : color & 0x0F; @@ -685,8 +841,8 @@ int16 *GfxScreen::unditherGetDitheredBgColors() { } void GfxScreen::debugShowMap(int mapNo) { - // We cannot really support changing maps when in upscaledHires mode - if (_upscaledHires) + // We cannot really support changing maps when display screen has a different resolution than visual screen + if ((_width != _displayWidth) || (_height != _displayHeight)) return; switch (mapNo) { @@ -779,8 +935,8 @@ void GfxScreen::adjustBackUpscaledCoordinates(int16 &y, int16 &x, Sci32ViewNativ switch (_upscaledHires) { case GFX_SCREEN_UPSCALED_480x300: - x = (x << 1) / 3; - y = (y << 1) / 3; + x = (x * 4) / 6; + y = (y * 4) / 6; break; case GFX_SCREEN_UPSCALED_640x400: x /= 2; @@ -816,26 +972,4 @@ int16 GfxScreen::kernelPicNotValid(int16 newPicNotValid) { return oldPicNotValid; } -uint16 GfxScreen::getLowResScreenHeight() { - // Some Mac SCI1/1.1 games only take up 190 rows and do not - // have the menu bar. - // TODO: Verify that LSL1 and LSL5 use height 190 - if (g_sci->getPlatform() == Common::kPlatformMacintosh) { - switch (g_sci->getGameId()) { - case GID_FREDDYPHARKAS: - case GID_KQ5: - case GID_KQ6: - case GID_LSL1: - case GID_LSL5: - case GID_SQ1: - return 190; - default: - break; - } - } - - // Everything else is 200 - return 200; -} - } // End of namespace Sci diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h index e266a4ed16..766e32614a 100644 --- a/engines/sci/graphics/screen.h +++ b/engines/sci/graphics/screen.h @@ -69,6 +69,8 @@ public: uint16 getWidth() { return _width; } uint16 getHeight() { return _height; } + uint16 getScriptWidth() { return _scriptWidth; } + uint16 getScriptHeight() { return _scriptHeight; } uint16 getDisplayWidth() { return _displayWidth; } uint16 getDisplayHeight() { return _displayHeight; } byte getColorWhite() { return _colorWhite; } @@ -81,11 +83,51 @@ public: void copyDisplayRectToScreen(const Common::Rect &rect); void copyRectToScreen(const Common::Rect &rect, int16 x, int16 y); + // calls to code pointers + void inline vectorAdjustCoordinate (int16 *x, int16 *y) { + (this->*_vectorAdjustCoordinatePtr)(x, y); + } + void inline vectorAdjustLineCoordinates (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) { + (this->*_vectorAdjustLineCoordinatesPtr)(left, top, right, bottom, drawMask, color, priority, control); + } + byte inline vectorIsFillMatch (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) { + return (this->*_vectorIsFillMatchPtr)(x, y, screenMask, t_color, t_pri, t_con, isEGA); + } + void inline vectorPutPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + (this->*_vectorPutPixelPtr)(x, y, drawMask, color, priority, control); + } + void inline vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + (this->*_vectorPutLinePixelPtr)(x, y, drawMask, color, priority, control); + } + byte inline vectorGetVisual(int16 x, int16 y) { + return (this->*_vectorGetPixelPtr)(_visualScreen, x, y); + } + byte inline vectorGetPriority(int16 x, int16 y) { + return (this->*_vectorGetPixelPtr)(_priorityScreen, x, y); + } + byte inline vectorGetControl(int16 x, int16 y) { + return (this->*_vectorGetPixelPtr)(_controlScreen, x, y); + } + + + void inline putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + (this->*_putPixelPtr)(x, y, drawMask, color, priority, control); + } + + byte inline getVisual(int16 x, int16 y) { + return (this->*_getPixelPtr)(_visualScreen, x, y); + } + byte inline getPriority(int16 x, int16 y) { + return (this->*_getPixelPtr)(_priorityScreen, x, y); + } + byte inline getControl(int16 x, int16 y) { + return (this->*_getPixelPtr)(_controlScreen, x, y); + } + byte getDrawingMask(byte color, byte prio, byte control); - void putPixel(int x, int y, byte drawMask, byte color, byte prio, byte control); - void putFontPixel(int startingY, int x, int y, byte color); - void putPixelOnDisplay(int x, int y, byte color); - void putScaledPixelOnDisplay(int x, int y, byte color); + //void putPixel(int16 x, int16 y, byte drawMask, byte color, byte prio, byte control); + void putFontPixel(int16 startingY, int16 x, int16 y, byte color); + void putPixelOnDisplay(int16 x, int16 y, byte color); void drawLine(Common::Point startPoint, Common::Point endPoint, byte color, byte prio, byte control); void drawLine(int16 left, int16 top, int16 right, int16 bottom, byte color, byte prio, byte control) { drawLine(Common::Point(left, top), Common::Point(right, bottom), color, prio, control); @@ -101,10 +143,6 @@ public: void enableUndithering(bool flag); void putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, uint16 chr, byte color); - byte getVisual(int x, int y); - byte getPriority(int x, int y); - byte getControl(int x, int y); - byte isFillMatch(int16 x, int16 y, byte drawMask, byte t_color, byte t_pri, byte t_con, bool isEGA); int bitsGetDataSize(Common::Rect rect, byte mask); void bitsSave(Common::Rect rect, byte mask, byte *memoryPtr); @@ -135,9 +173,10 @@ public: private: uint16 _width; - uint16 _pitch; uint16 _height; uint _pixels; + uint16 _scriptWidth; + uint16 _scriptHeight; uint16 _displayWidth; uint16 _displayHeight; uint _displayPixels; @@ -190,8 +229,8 @@ private: * This here holds a translation for vertical+horizontal coordinates between native * (visual) and actual (display) screen. */ - int _upscaledHeightMapping[SCI_SCREEN_UPSCALEDMAXHEIGHT + 1]; - int _upscaledWidthMapping[SCI_SCREEN_UPSCALEDMAXWIDTH + 1]; + int16 _upscaledHeightMapping[SCI_SCREEN_UPSCALEDMAXHEIGHT + 1]; + int16 _upscaledWidthMapping[SCI_SCREEN_UPSCALEDMAXWIDTH + 1]; /** * This defines whether or not the font we're drawing is already scaled @@ -199,7 +238,38 @@ private: */ bool _fontIsUpscaled; - uint16 getLowResScreenHeight(); + // dynamic code + void (GfxScreen::*_vectorAdjustCoordinatePtr) (int16 *x, int16 *y); + void vectorAdjustCoordinateNOP (int16 *x, int16 *y); + void vectorAdjustCoordinate480x300Mac (int16 *x, int16 *y); + + void (GfxScreen::*_vectorAdjustLineCoordinatesPtr) (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control); + void vectorAdjustLineCoordinatesNOP (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control); + void vectorAdjustLineCoordinates480x300Mac (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control); + + byte (GfxScreen::*_vectorIsFillMatchPtr) (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA); + byte vectorIsFillMatchNormal (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA); + byte vectorIsFillMatch480x300Mac (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA); + + void (GfxScreen::*_vectorPutPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + void vectorPutPixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + + void (GfxScreen::*_vectorPutLinePixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + void vectorPutLinePixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + + byte (GfxScreen::*_vectorGetPixelPtr) (byte *screen, int16 x, int16 y); + + void (GfxScreen::*_putPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + void putPixelNormal (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + void putPixelDisplayUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + void putPixelAllUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + + byte (GfxScreen::*_getPixelPtr) (byte *screen, int16 x, int16 y); + byte getPixelNormal (byte *screen, int16 x, int16 y); + byte getPixelUpscaled (byte *screen, int16 x, int16 y); + + // pixel helper + void putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte color); }; } // End of namespace Sci diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp index 245d6996cb..f463dff4b1 100644 --- a/engines/sci/graphics/text16.cpp +++ b/engines/sci/graphics/text16.cpp @@ -143,18 +143,30 @@ int16 GfxText16::CodeProcessing(const char *&text, GuiResourceId orgFontId, int1 return textCodeSize; } -static const uint16 text16_punctuationSjis[] = { - 0x9F82, 0xA182, 0xA382, 0xA582, 0xA782, 0xC182, 0xA782, 0xC182, 0xE182, 0xE382, 0xE582, 0xEC82, - 0x4083, 0x4283, 0x4483, 0x4683, 0x4883, 0x6283, 0x8383, 0x8583, 0x8783, 0x8E83, 0x9583, 0x9683, - 0x5B81, 0x4181, 0x4281, 0x7681, 0x7881, 0x4981, 0x4881, 0 +// Has actually punctuation and characters in it, that may not be the first in a line +static const uint16 text16_shiftJIS_punctuation[] = { + 0x9F82, 0xA182, 0xA382, 0xA582, 0xA782, 0xC182, 0xE182, 0xE382, 0xE582, 0xEC82, 0x4083, 0x4283, + 0x4483, 0x4683, 0x4883, 0x6283, 0x8383, 0x8583, 0x8783, 0x8E83, 0x9583, 0x9683, 0x5B81, 0x4181, + 0x4281, 0x7681, 0x7881, 0x4981, 0x4881, 0 }; // return max # of chars to fit maxwidth with full words, does not include // breaking space -int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgFontId) { +// Also adjusts text pointer to the new position for the caller +// +// Special cases in games: +// Laura Bow 2 - Credits in the game menu - all the text lines start with spaces (bug #5159) +// Act 6 Coroner questionaire - the text of all control buttons has trailing spaces +// "Detective Ryan Hanrahan O'Riley" contains even more spaces (bug #5334) +// Conquests of Camelot - talking with Cobb - one text box of the dialogue contains a longer word, +// that will be broken into 2 lines (bug #5159) +int16 GfxText16::GetLongest(const char *&textPtr, int16 maxWidth, GuiResourceId orgFontId) { uint16 curChar = 0; - int16 maxChars = 0, curCharCount = 0; - uint16 width = 0; + const char *textStartPtr = textPtr; + const char *lastSpacePtr = NULL; + int16 lastSpaceCharCount = 0; + int16 curCharCount = 0, resultCharCount = 0; + uint16 curWidth = 0, tempWidth = 0; GuiResourceId previousFontId = GetFontId(); int16 previousPenColor = _ports->_curPort->penClr; @@ -162,35 +174,38 @@ int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgF if (!_font) return 0; - while (width <= maxWidth) { - curChar = (*(const byte *)text++); + while (1) { + curChar = (*(const byte *)textPtr); if (_font->isDoubleByte(curChar)) { - curChar |= (*(const byte *)text++) << 8; - curCharCount++; + curChar |= (*(const byte *)(textPtr + 1)) << 8; } switch (curChar) { case 0x7C: if (getSciVersion() >= SCI_VERSION_1_1) { - curCharCount++; - curCharCount += CodeProcessing(text, orgFontId, previousPenColor, false); + curCharCount++; textPtr++; + curCharCount += CodeProcessing(textPtr, orgFontId, previousPenColor, false); continue; } break; // We need to add 0xD, 0xA and 0xD 0xA to curCharCount and then exit - // which means, we split text like - // 'Mature, experienced software analyst available.' 0xD 0xA - // 'Bug installation a proven speciality. "No version too clean."' (normal game text, this is from lsl2) - // and 0xA '-------' 0xA (which is the official sierra subtitle separator) + // which means, we split text like for example + // - 'Mature, experienced software analyst available.' 0xD 0xA + // 'Bug installation a proven speciality. "No version too clean."' (normal game text, this is from lsl2) + // - 0xA '-------' 0xA (which is the official sierra subtitle separator) (found in multilingual versions) // Sierra did it the same way. case 0xD: // Check, if 0xA is following, if so include it as well - if ((*(const unsigned char *)text) == 0xA) - curCharCount++; + if ((*(const byte *)(textPtr + 1)) == 0xA) { + curCharCount++; textPtr++; + } // it's meant to pass through here case 0xA: case 0x9781: // this one is used by SQ4/japanese as line break as well - curCharCount++; + curCharCount++; textPtr++; + if (curChar > 0xFF) { + curCharCount++; textPtr++; + } // and it's also meant to pass through here case 0: SetFont(previousFontId); @@ -198,55 +213,86 @@ int16 GfxText16::GetLongest(const char *text, int16 maxWidth, GuiResourceId orgF return curCharCount; case ' ': - maxChars = curCharCount; // return count up to (but not including) breaking space + lastSpaceCharCount = curCharCount; // return count up to (but not including) breaking space + lastSpacePtr = textPtr + 1; // remember position right after the current space break; } - // Sometimes this can go off the screen, like for example bug #3040161. - // However, we only perform this for non-Japanese games, as these require - // special handling, done after this loop. - if (width + _font->getCharWidth(curChar) > maxWidth && g_sci->getLanguage() != Common::JA_JPN) + tempWidth += _font->getCharWidth(curChar); + + // Width is too large? -> break out + if (tempWidth > maxWidth) break; - width += _font->getCharWidth(curChar); - curCharCount++; + + // still fits, remember width + curWidth = tempWidth; + + // go to next character + curCharCount++; textPtr++; + if (curChar > 0xFF) { + // Double-Byte + curCharCount++; textPtr++; + } } - // Text without spaces, probably Kanji/Japanese - if (maxChars == 0) { - maxChars = curCharCount; + if (lastSpaceCharCount) { + // Break and at least one space was found before that + resultCharCount = lastSpaceCharCount; - uint16 nextChar; + // additionally skip over all spaces, that are following that space, but don't count them for displaying purposes + textPtr = lastSpacePtr; + while (*textPtr == ' ') + textPtr++; - // We remove the last char only, if maxWidth was actually equal width - // before adding the last char. Otherwise we won't get the same cutting - // as in sierra pc98 sci. - if (maxWidth == (width - _font->getCharWidth(curChar))) { - maxChars--; - if (curChar > 0xFF) - maxChars--; - nextChar = curChar; - } else { - nextChar = (*(const byte *)text++); - if (_font->isDoubleByte(nextChar)) - nextChar |= (*(const byte *)text++) << 8; - } - // sierra checked the following character against a punctuation kanji table - if (nextChar > 0xFF) { - // if the character is punctuation, we go back one character - uint nonBreakingNr = 0; - while (text16_punctuationSjis[nonBreakingNr]) { - if (text16_punctuationSjis[nonBreakingNr] == nextChar) { - maxChars--; - if (curChar > 0xFF) - maxChars--; // go back 2 chars, when last char was double byte + } else { + // Break without spaces found, we split the very first word - may also be Kanji/Japanese + if (curChar > 0xFF) { + // current charracter is Japanese + + // PC-9801 SCI actually added the last character, which shouldn't fit anymore, still onto the + // screen in case maxWidth wasn't fully reached with the last character + if (( maxWidth - 1 ) > curWidth) { + curCharCount += 2; textPtr += 2; + + curChar = (*(const byte *)textPtr); + if (_font->isDoubleByte(curChar)) { + curChar |= (*(const byte *)(textPtr + 1)) << 8; + } + } + + // But it also checked, if the current character is not inside a punctuation table and it even + // went backwards in case it found multiple ones inside that table. + uint nonBreakingPos = 0; + + while (1) { + // Look up if character shouldn't be the first on a new line + nonBreakingPos = 0; + while (text16_shiftJIS_punctuation[nonBreakingPos]) { + if (text16_shiftJIS_punctuation[nonBreakingPos] == curChar) + break; + nonBreakingPos++; + } + if (!text16_shiftJIS_punctuation[nonBreakingPos]) { + // character is fine break; } - nonBreakingNr++; + // Character is not acceptable, seek backward in the text + curCharCount -= 2; textPtr -= 2; + if (textPtr < textStartPtr) + error("Seeking back went too far, data corruption?"); + + curChar = (*(const byte *)textPtr); + if (!_font->isDoubleByte(curChar)) + error("Non double byte while seeking back"); + curChar |= (*(const byte *)(textPtr + 1)) << 8; } } + + // We split the word in that case + resultCharCount = curCharCount; } SetFont(previousFontId); _ports->penColor(previousPenColor); - return maxChars; + return resultCharCount; } void GfxText16::Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont) { @@ -303,7 +349,7 @@ void GfxText16::DrawString(const char *str, GuiResourceId orgFontId, int16 orgPe Draw(str, 0, (int16)strlen(str), orgFontId, orgPenColor); } -int16 GfxText16::Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth) { +int16 GfxText16::Size(Common::Rect &rect, const char *text, uint16 languageSplitter, GuiResourceId fontId, int16 maxWidth) { GuiResourceId previousFontId = GetFontId(); int16 previousPenColor = _ports->_curPort->penClr; int16 charCount; @@ -315,12 +361,12 @@ int16 GfxText16::Size(Common::Rect &rect, const char *text, GuiResourceId fontId else fontId = previousFontId; - if (g_sci->getLanguage() == Common::JA_JPN) - SwitchToFont900OnSjis(text); - rect.top = rect.left = 0; if (maxWidth < 0) { // force output as single line + if (g_sci->getLanguage() == Common::JA_JPN) + SwitchToFont900OnSjis(text, languageSplitter); + StringWidth(text, fontId, textWidth, textHeight); rect.bottom = textHeight; rect.right = textWidth; @@ -328,17 +374,20 @@ int16 GfxText16::Size(Common::Rect &rect, const char *text, GuiResourceId fontId // rect.right=found widest line with RTextWidth and GetLongest // rect.bottom=num. lines * GetPointSize rect.right = (maxWidth ? maxWidth : 192); - const char *curPos = text; - while (*curPos) { - charCount = GetLongest(curPos, rect.right, fontId); + const char *curTextPos = text; // in work position for GetLongest() + const char *curTextLine = text; // starting point of current line + while (*curTextPos) { + // We need to check for Shift-JIS every line + if (g_sci->getLanguage() == Common::JA_JPN) + SwitchToFont900OnSjis(curTextPos, languageSplitter); + + charCount = GetLongest(curTextPos, rect.right, fontId); if (charCount == 0) break; - Width(curPos, 0, charCount, fontId, textWidth, textHeight, false); + Width(curTextLine, 0, charCount, fontId, textWidth, textHeight, false); maxTextWidth = MAX(textWidth, maxTextWidth); totalHeight += textHeight; - curPos += charCount; - while (*curPos == ' ') - curPos++; // skip over breaking spaces + curTextLine = curTextPos; } rect.bottom = totalHeight; rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth); @@ -405,34 +454,38 @@ void GfxText16::Show(const char *text, int16 from, int16 len, GuiResourceId orgF } // Draws a text in rect. -void GfxText16::Box(const char *text, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId) { +void GfxText16::Box(const char *text, uint16 languageSplitter, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId) { int16 textWidth, maxTextWidth, textHeight, charCount; int16 offset = 0; int16 hline = 0; GuiResourceId previousFontId = GetFontId(); int16 previousPenColor = _ports->_curPort->penClr; bool doubleByteMode = false; + const char *curTextPos = text; + const char *curTextLine = text; if (fontId != -1) SetFont(fontId); else fontId = previousFontId; - if (g_sci->getLanguage() == Common::JA_JPN) { - if (SwitchToFont900OnSjis(text)) - doubleByteMode = true; - } - // Reset reference code rects _codeRefRects.clear(); _codeRefTempRect.left = _codeRefTempRect.top = -1; maxTextWidth = 0; - while (*text) { - charCount = GetLongest(text, rect.width(), fontId); + while (*curTextPos) { + // We need to check for Shift-JIS every line + // Police Quest 2 PC-9801 often draws English + Japanese text during the same call + if (g_sci->getLanguage() == Common::JA_JPN) { + if (SwitchToFont900OnSjis(curTextPos, languageSplitter)) + doubleByteMode = true; + } + + charCount = GetLongest(curTextPos, rect.width(), fontId); if (charCount == 0) break; - Width(text, 0, charCount, fontId, textWidth, textHeight, true); + Width(curTextLine, 0, charCount, fontId, textWidth, textHeight, true); maxTextWidth = MAX<int16>(maxTextWidth, textWidth); switch (alignment) { case SCI_TEXT16_ALIGNMENT_RIGHT: @@ -451,15 +504,13 @@ void GfxText16::Box(const char *text, bool show, const Common::Rect &rect, TextA _ports->moveTo(rect.left + offset, rect.top + hline); if (show) { - Show(text, 0, charCount, fontId, previousPenColor); + Show(curTextLine, 0, charCount, fontId, previousPenColor); } else { - Draw(text, 0, charCount, fontId, previousPenColor); + Draw(curTextLine, 0, charCount, fontId, previousPenColor); } hline += textHeight; - text += charCount; - while (*text == ' ') - text++; // skip over breaking spaces + curTextLine = curTextPos; } SetFont(previousFontId); _ports->penColor(previousPenColor); @@ -521,11 +572,13 @@ void GfxText16::DrawStatus(const char *text) { // Sierra did this in their PC98 interpreter only, they identify a text as being // sjis and then switch to font 900 -bool GfxText16::SwitchToFont900OnSjis(const char *text) { +bool GfxText16::SwitchToFont900OnSjis(const char *text, uint16 languageSplitter) { byte firstChar = (*(const byte *)text++); - if (((firstChar >= 0x81) && (firstChar <= 0x9F)) || ((firstChar >= 0xE0) && (firstChar <= 0xEF))) { - SetFont(900); - return true; + if (languageSplitter != 0x6a23) { // #j prefix as language splitter + if (((firstChar >= 0x81) && (firstChar <= 0x9F)) || ((firstChar >= 0xE0) && (firstChar <= 0xEF))) { + SetFont(900); + return true; + } } return false; } @@ -554,9 +607,9 @@ reg_t GfxText16::allocAndFillReferenceRectArray() { return NULL_REG; } -void GfxText16::kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) { +void GfxText16::kernelTextSize(const char *text, uint16 languageSplitter, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) { Common::Rect rect(0, 0, 0, 0); - Size(rect, text, font, maxWidth); + Size(rect, text, languageSplitter, font, maxWidth); *textWidth = rect.width(); *textHeight = rect.height(); } diff --git a/engines/sci/graphics/text16.h b/engines/sci/graphics/text16.h index ab0cb13a64..2724d97347 100644 --- a/engines/sci/graphics/text16.h +++ b/engines/sci/graphics/text16.h @@ -51,15 +51,20 @@ public: void ClearChar(int16 chr); - int16 GetLongest(const char *text, int16 maxWidth, GuiResourceId orgFontId); + int16 GetLongest(const char *&text, int16 maxWidth, GuiResourceId orgFontId); void Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont); void StringWidth(const char *str, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight); void ShowString(const char *str, GuiResourceId orgFontId, int16 orgPenColor); void DrawString(const char *str, GuiResourceId orgFontId, int16 orgPenColor); - int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth); + int16 Size(Common::Rect &rect, const char *text, uint16 textLanguage, GuiResourceId fontId, int16 maxWidth); void Draw(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 orgPenColor); void Show(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 orgPenColor); - void Box(const char *text, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId); + void Box(const char *text, uint16 languageSplitter, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId); + + void Box(const char *text, bool show, const Common::Rect &rect, TextAlignment alignment, GuiResourceId fontId) { + Box(text, 0, show, rect, alignment, fontId); + } + void DrawString(const char *text); void DrawStatus(const char *text); @@ -67,13 +72,13 @@ public: reg_t allocAndFillReferenceRectArray(); - void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); + void kernelTextSize(const char *text, uint16 textLanguage, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); void kernelTextFonts(int argc, reg_t *argv); void kernelTextColors(int argc, reg_t *argv); private: void init(); - bool SwitchToFont900OnSjis(const char *text); + bool SwitchToFont900OnSjis(const char *text, uint16 languageSplitter); GfxCache *_cache; GfxPorts *_ports; diff --git a/engines/sci/graphics/transitions.cpp b/engines/sci/graphics/transitions.cpp index 5e7dbc6c15..ccc7a4389a 100644 --- a/engines/sci/graphics/transitions.cpp +++ b/engines/sci/graphics/transitions.cpp @@ -339,10 +339,10 @@ void GfxTransitions::pixelation(bool blackoutFlag) { do { mask = (mask & 1) ? (mask >> 1) ^ 0xB400 : mask >> 1; - if (mask >= _screen->getWidth() * _screen->getHeight()) + if (mask >= _screen->getScriptWidth() * _screen->getScriptHeight()) continue; - pixelRect.left = mask % _screen->getWidth(); pixelRect.right = pixelRect.left + 1; - pixelRect.top = mask / _screen->getWidth(); pixelRect.bottom = pixelRect.top + 1; + pixelRect.left = mask % _screen->getScriptWidth(); pixelRect.right = pixelRect.left + 1; + pixelRect.top = mask / _screen->getScriptWidth(); pixelRect.bottom = pixelRect.top + 1; pixelRect.clip(_picRect); if (!pixelRect.isEmpty()) copyRectToScreen(pixelRect, blackoutFlag); diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp index f3f352e5b8..da61ecf4c3 100644 --- a/engines/sci/graphics/view.cpp +++ b/engines/sci/graphics/view.cpp @@ -283,6 +283,7 @@ void GfxView::initData(GuiResourceId resourceId) { _isScaleable = false; break; case 0x40: + case 0x4F: // LSL6 Polish, seems to be garbage - bug #6718 case 0: break; // don't do anything, we already have _isScaleable set default: @@ -366,7 +367,7 @@ void GfxView::initData(GuiResourceId resourceId) { default: error("ViewType was not detected, can't continue"); } - + // Inject our own views // Currently only used for Dual mode (speech + text) for games, that do not have a "dual" icon already // Which is Laura Bow 2 + King's Quest 6 diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 6b6058c819..33392e3b42 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -76,8 +76,8 @@ MODULE_OBJS := \ sound/drivers/midi.o \ sound/drivers/pcjr.o \ video/seq_decoder.o - - + + ifdef ENABLE_SCI32 MODULE_OBJS += \ engine/kgraphics32.o \ diff --git a/engines/sci/parser/said.cpp b/engines/sci/parser/said.cpp index 693bbec744..27ebc58704 100644 --- a/engines/sci/parser/said.cpp +++ b/engines/sci/parser/said.cpp @@ -186,8 +186,7 @@ static bool parseList(ParseTreeNode* parentNode); static bool parseListEntry(ParseTreeNode* parentNode); static bool parseWord(ParseTreeNode* parentNode); -static bool parseWord(ParseTreeNode* parentNode) -{ +static bool parseWord(ParseTreeNode* parentNode) { int token = said_tokens[said_token]; if (token & 0x8000) return false; @@ -201,8 +200,7 @@ static bool parseWord(ParseTreeNode* parentNode) return true; } -static bool parsePart2(ParseTreeNode* parentNode, bool& nonempty) -{ +static bool parsePart2(ParseTreeNode* parentNode, bool& nonempty) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -259,8 +257,7 @@ static bool parsePart2(ParseTreeNode* parentNode, bool& nonempty) return false; } -static bool parsePart3(ParseTreeNode* parentNode, bool& nonempty) -{ +static bool parsePart3(ParseTreeNode* parentNode, bool& nonempty) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -318,8 +315,7 @@ static bool parsePart3(ParseTreeNode* parentNode, bool& nonempty) } -static bool parseSlash(ParseTreeNode* parentNode) -{ +static bool parseSlash(ParseTreeNode* parentNode) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -343,8 +339,7 @@ static bool parseSlash(ParseTreeNode* parentNode) } -static bool parseRef(ParseTreeNode* parentNode) -{ +static bool parseRef(ParseTreeNode* parentNode) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -411,8 +406,7 @@ static bool parseRef(ParseTreeNode* parentNode) return false; } -static bool parseComma(ParseTreeNode* parentNode) -{ +static bool parseComma(ParseTreeNode* parentNode) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -435,8 +429,7 @@ static bool parseComma(ParseTreeNode* parentNode) return false; } -static bool parseListEntry(ParseTreeNode* parentNode) -{ +static bool parseListEntry(ParseTreeNode* parentNode) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -494,8 +487,7 @@ static bool parseListEntry(ParseTreeNode* parentNode) return false; } -static bool parseList(ParseTreeNode* parentNode) -{ +static bool parseList(ParseTreeNode* parentNode) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -524,8 +516,7 @@ static bool parseList(ParseTreeNode* parentNode) return false; } -static bool parseExpr(ParseTreeNode* parentNode) -{ +static bool parseExpr(ParseTreeNode* parentNode) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -546,7 +537,6 @@ static bool parseExpr(ParseTreeNode* parentNode) said_attach_subtree(newParent, 0x141, 0x14F, newNode); newParent = newParent->right; - } found = parseRef(newParent); @@ -561,8 +551,7 @@ static bool parseExpr(ParseTreeNode* parentNode) return false; } -static bool parseSpec(ParseTreeNode* parentNode) -{ +static bool parseSpec(ParseTreeNode* parentNode) { // Store current state for rolling back if we fail int curToken = said_token; int curTreePos = said_tree_pos; @@ -748,9 +737,7 @@ static void node_print_desc(ParseTreeNode *) { } - -static int matchTrees(ParseTreeNode* parseT, ParseTreeNode* saidT) -{ +static int matchTrees(ParseTreeNode* parseT, ParseTreeNode* saidT) { outputDepth++; scidprintf("%*smatchTrees on ", outputDepth, ""); node_print_desc(parseT); diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp index b4a223dcff..000b037b44 100644 --- a/engines/sci/parser/vocabulary.cpp +++ b/engines/sci/parser/vocabulary.cpp @@ -530,18 +530,16 @@ bool Vocabulary::tokenizeString(ResultWordListList &retval, const char *sentence *error = NULL; do { - c = sentence[pos_in_sentence++]; + if (Common::isAlnum(c) || (c == '-' && wordLen) || (c >= 0x80)) { currentWord[wordLen] = lowerCaseMap[c]; ++wordLen; - } - // Continue on this word */ - // Words may contain a '-', but may not - // start with one. - else { - if (wordLen) { // Finished a word? + } else if (c == ' ' || c == '\0') { + // Continue on this word. Words may contain a '-', but may not start with + // one. + if (wordLen) { // Finished a word? ResultWordList lookup_result; // Look it up diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 17195c14b8..10740a8b7b 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -55,6 +55,11 @@ SciVersion getSciVersion() { return s_sciVersion; } +SciVersion getSciVersionForDetection() { + assert(!g_sci); + return s_sciVersion; +} + const char *getSciVersionDesc(SciVersion version) { switch (version) { case SCI_VERSION_NONE: @@ -88,9 +93,6 @@ const char *getSciVersionDesc(SciVersion version) { ////////////////////////////////////////////////////////////////////// - -#undef SCI_REQUIRE_RESOURCE_FILES - //#define SCI_VERBOSE_RESMAN 1 static const char *const s_errorDescriptions[] = { @@ -639,7 +641,7 @@ int ResourceManager::addAppropriateSources() { return 1; } -int ResourceManager::addAppropriateSources(const Common::FSList &fslist) { +int ResourceManager::addAppropriateSourcesForDetection(const Common::FSList &fslist) { ResourceSource *map = 0; Common::Array<ResourceSource *> sci21Maps; @@ -858,7 +860,7 @@ void ResourceManager::freeResourceSources() { ResourceManager::ResourceManager() { } -void ResourceManager::init(bool initFromFallbackDetector) { +void ResourceManager::init() { _memoryLocked = 0; _memoryLRU = 0; _LRU.clear(); @@ -890,25 +892,24 @@ void ResourceManager::init(bool initFromFallbackDetector) { debugC(1, kDebugLevelResMan, "resMan: Detected volume version %d: %s", _volVersion, versionDescription(_volVersion)); if ((_mapVersion == kResVersionUnknown) && (_volVersion == kResVersionUnknown)) { - warning("Volume and map version not detected, assuming that this is not a sci game"); + warning("Volume and map version not detected, assuming that this is not a SCI game"); _viewType = kViewUnknown; return; } scanNewSources(); - if (!initFromFallbackDetector) { - if (!addAudioSources()) { - // FIXME: This error message is not always correct. - // OTOH, it is nice to be able to detect missing files/sources - // So we should definitely fix addAudioSources so this error - // only pops up when necessary. Disabling for now. - //error("Somehow I can't seem to find the sound files I need (RESOURCE.AUD/RESOURCE.SFX), aborting"); - } - addScriptChunkSources(); - scanNewSources(); + if (!addAudioSources()) { + // FIXME: This error message is not always correct. + // OTOH, it is nice to be able to detect missing files/sources + // So we should definitely fix addAudioSources so this error + // only pops up when necessary. Disabling for now. + //error("Somehow I can't seem to find the sound files I need (RESOURCE.AUD/RESOURCE.SFX), aborting"); } + addScriptChunkSources(); + scanNewSources(); + detectSciVersion(); debugC(1, kDebugLevelResMan, "resMan: Detected %s", getSciVersionDesc(getSciVersion())); @@ -943,6 +944,22 @@ void ResourceManager::init(bool initFromFallbackDetector) { } } +void ResourceManager::initForDetection() { + assert(!g_sci); + + _memoryLocked = 0; + _memoryLRU = 0; + _LRU.clear(); + _resMap.clear(); + _audioMapSCI1 = NULL; + + _mapVersion = detectMapVersion(); + _volVersion = detectVolVersion(); + + scanNewSources(); + detectSciVersion(); +} + ResourceManager::~ResourceManager() { // freeing resources ResourceMap::iterator itr = _resMap.begin(); @@ -1645,6 +1662,9 @@ int ResourceManager::readResourceMapSCI1(ResourceSource *map) { do { type = fileStream->readByte() & 0x1F; resMap[type].wOffset = fileStream->readUint16LE(); + if (fileStream->eos()) + return SCI_ERROR_RESMAP_NOT_FOUND; + resMap[prevtype].wSize = (resMap[type].wOffset - resMap[prevtype].wOffset) / nEntrySize; prevtype = type; @@ -2330,6 +2350,9 @@ bool ResourceManager::detectPaletteMergingSci11() { // Old palette format used in palette resource? -> it's merging if ((data[0] == 0 && data[1] == 1) || (data[0] == 0 && data[1] == 0 && READ_LE_UINT16(data + 29) == 0)) return true; + // Hardcoded: Laura Bow 2 floppy uses new palette resource, but still palette merging + 16 bit color matching + if ((g_sci->getGameId() == GID_LAURABOW2) && (!g_sci->isCD()) && (!g_sci->isDemo())) + return true; return false; } return false; diff --git a/engines/sci/resource.h b/engines/sci/resource.h index e90f52a3ce..62f3c584ac 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -312,10 +312,22 @@ public: /** * Initializes the resource manager. */ - void init(bool initFromFallbackDetector = false); + void init(); + /** + * Similar to the function above, only called from the fallback detector + */ + void initForDetection(); + + /** + * Adds all of the resource files for a game + */ int addAppropriateSources(); - int addAppropriateSources(const Common::FSList &fslist); // TODO: Switch from FSList to Common::Archive? + + /** + * Similar to the function above, only called from the fallback detector + */ + int addAppropriateSourcesForDetection(const Common::FSList &fslist); // TODO: Switch from FSList to Common::Archive? /** * Looks up a resource's data. diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index 4c7cd9b84a..c775f502c5 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -110,7 +110,7 @@ bool Resource::loadFromAudioVolumeSCI11(Common::SeekableReadStream *file) { unalloc(); return false; } - + _headerSize = file->readByte(); if (type == kResourceTypeAudio) { @@ -710,7 +710,6 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers // 0x20 is set on rhythm channels to prevent remapping // CHECKME: Which SCI versions need that set manually? - channel->flags = (*channel->data) >> 4; if (channel->number == 9) channel->flags |= 2; // Note: flag 1: channel start offset is 0 instead of 10 diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index fc723f18cf..60a1271b89 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -219,7 +219,7 @@ Common::Error SciEngine::run() { // Add the after market GM patches for the specified game, if they exist _resMan->addNewGMPatch(_gameId); _gameObjectAddress = _resMan->findGameObject(); - + _scriptPatcher = new ScriptPatcher(); SegManager *segMan = new SegManager(_resMan, _scriptPatcher); @@ -896,7 +896,7 @@ void SciEngine::syncSoundSettings() { bool SciEngine::speechAndSubtitlesEnabled() { bool subtitlesOn = ConfMan.getBool("subtitles"); bool speechOn = !ConfMan.getBool("speech_mute"); - + if (isCD() && subtitlesOn && speechOn) return true; return false; @@ -936,7 +936,7 @@ void SciEngine::updateScummVMAudioOptions() { // depending on the in-game settings if (isCD() && getSciVersion() == SCI_VERSION_1_1) { uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][90].getOffset(); - + switch (ingameSetting) { case 1: // subtitles diff --git a/engines/sci/sci.h b/engines/sci/sci.h index 48bc4819d2..4928fd1b4e 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -314,13 +314,16 @@ public: * if NULL is passed no subtitle will be added to the returned string * @return processed string */ - Common::String strSplit(const char *str, const char *sep = "\r----------\r"); + Common::String strSplitLanguage(const char *str, uint16 *splitLanguage, const char *sep = "\r----------\r"); + Common::String strSplit(const char *str, const char *sep = "\r----------\r") { + return strSplitLanguage(str, NULL, sep); + } kLanguage getSciLanguage(); void setSciLanguage(kLanguage lang); void setSciLanguage(); - Common::String getSciLanguageString(const Common::String &str, kLanguage lang, kLanguage *lang2 = NULL) const; + Common::String getSciLanguageString(const Common::String &str, kLanguage lang, kLanguage *lang2 = NULL, uint16 *languageSplitter = NULL) const; // Check if vocabulary needs to get switched (in multilingual parser games) void checkVocabularySwitch(); @@ -429,6 +432,12 @@ extern SciEngine *g_sci; SciVersion getSciVersion(); /** + * Same as above, but this version doesn't assert on unknown SCI versions. + * Only used by the fallback detector + */ +SciVersion getSciVersionForDetection(); + +/** * Convenience function converting an SCI version into a human-readable string. */ const char *getSciVersionDesc(SciVersion version); diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp index 31c9d90de8..baf85de74c 100644 --- a/engines/sci/sound/drivers/midi.cpp +++ b/engines/sci/sound/drivers/midi.cpp @@ -398,26 +398,43 @@ void MidiPlayer_Midi::playSwitch(bool play) { } } -bool MidiPlayer_Midi::isMt32GmPatch(const byte *data, int size) -{ - if (size < 1155) +bool MidiPlayer_Midi::isMt32GmPatch(const byte *data, int size) { + // WORKAROUND: Some Mac games (e.g. LSL5) may have an extra byte at the + // end, so compensate for that here - bug #6725. + if (size == 16890) + size--; + + // Need at least 1153 + 2 bytes for a GM patch. Check readMt32GmPatch() + // below for more info. + if (size < 1153 + 2) return false; + // The maximum number of bytes for an MT-32 patch is 16889. The maximum + // number of timbres is 64, which leads us to: + // 491 + 1 + 64 * 246 + 653 = 16889 if (size > 16889) return true; bool isMt32 = false; bool isMt32Gm = false; + // First, check for a GM patch. The presence of MIDI data after the + // initial 1153 + 2 bytes indicates a GM patch if (READ_LE_UINT16(data + 1153) + 1155 == size) isMt32Gm = true; - int pos = 492 + 246 * data[491]; + // Now check for a regular MT-32 patch. Check readMt32Patch() below for + // more info. + // 491 = 20 + 20 + 20 + 2 + 1 + 11 + 3 * 11 + 256 + 128 + byte timbresNr = data[491]; + int pos = 492 + 246 * timbresNr; + // Patches 49-96 if ((size >= (pos + 386)) && (READ_BE_UINT16(data + pos) == 0xabcd)) - pos += 386; + pos += 386; // 256 + 128 + 2 + // Rhythm key map + partial reserve if ((size >= (pos + 267)) && (READ_BE_UINT16(data + pos) == 0xdcba)) - pos += 267; + pos += 267; // 256 + 9 + 2 if (size == pos) isMt32 = true; @@ -461,10 +478,28 @@ void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, const byte *buf, int len, } void MidiPlayer_Midi::readMt32Patch(const byte *data, int size) { + // MT-32 patch contents: + // - 20 bytes unkown + // - 20 bytes before-SysEx message + // - 20 bytes goodbye SysEx message + // - 2 bytes volume + // - 1 byte reverb + // - 11 bytes reverb Sysex message + // - 3 * 11 reverb data + // - 256 + 128 bytes patches 1-48 + // --> total: 491 bytes + // - 1 byte number of timbres (64 max) + // - 246 * timbres timbre data + // - 2 bytes flag (0xabcd) + // - 256 + 128 bytes patches 49-96 + // - 2 bytes flag (0xdcba) + // - 256 bytes rhythm key map + // - 9 bytes partial reserve + Common::MemoryReadStream *str = new Common::MemoryReadStream(data, size); // Send before-SysEx text - str->seek(0x14); + str->seek(20); sendMt32SysEx(0x200000, str, 20); // Save goodbye message @@ -528,15 +563,25 @@ void MidiPlayer_Midi::readMt32Patch(const byte *data, int size) { } void MidiPlayer_Midi::readMt32GmPatch(const byte *data, int size) { - memcpy(_patchMap, data, 0x80); - memcpy(_keyShift, data + 0x80, 0x80); - memcpy(_volAdjust, data + 0x100, 0x80); - memcpy(_percussionMap, data + 0x180, 0x80); - _channels[MIDI_RHYTHM_CHANNEL].volAdjust = data[0x200]; - memcpy(_velocityMapIdx, data + 0x201, 0x80); - memcpy(_velocityMap, data + 0x281, 0x200); - - uint16 midiSize = READ_LE_UINT16(data + 0x481); + // GM patch contents: + // - 128 bytes patch map + // - 128 bytes key shift + // - 128 bytes volume adjustment + // - 128 bytes percussion map + // - 1 byte volume adjust for the rhythm channel + // - 128 bytes velocity map IDs + // - 512 bytes velocity map + // --> total: 1153 bytes + + memcpy(_patchMap, data, 128); + memcpy(_keyShift, data + 128, 128); + memcpy(_volAdjust, data + 256, 128); + memcpy(_percussionMap, data + 384, 128); + _channels[MIDI_RHYTHM_CHANNEL].volAdjust = data[512]; + memcpy(_velocityMapIdx, data + 513, 128); + memcpy(_velocityMap, data + 641, 512); + + uint16 midiSize = READ_LE_UINT16(data + 1153); if (midiSize > 0) { if (size < midiSize + 1155) @@ -957,7 +1002,7 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) { if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY) warning("The automatic mapping for General MIDI hasn't been worked on for " "SCI1 games. Music might sound wrong or broken. Please choose another " - "music driver for this game (e.g. Adlib or MT-32) if you are " + "music driver for this game (e.g. AdLib or MT-32) if you are " "experiencing issues with music"); // Modify velocity map to make low velocity notes a little louder diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 362cca699d..7a6eaf62b4 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -142,7 +142,7 @@ void SciMusic::init() { _driverLastChannel = _pMidiDrv->getLastChannel(); if (getSciVersion() <= SCI_VERSION_0_LATE) _globalReverb = _pMidiDrv->getReverb(); // Init global reverb for SCI0 - + _currentlyPlayingSample = NULL; } diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h index 6149bb799e..4e44074630 100644 --- a/engines/sci/sound/music.h +++ b/engines/sci/sound/music.h @@ -264,7 +264,7 @@ private: int _driverFirstChannel; int _driverLastChannel; - + MusicEntry *_currentlyPlayingSample; }; |