/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "engines/obsolete.h" #include "common/savefile.h" #include "common/config-manager.h" #include "common/translation.h" #include "common/gui_options.h" #include "common/md5.h" #include "audio/mididrv.h" #include "scumm/he/intern_he.h" #include "scumm/scumm_v0.h" #include "scumm/scumm_v8.h" #include "scumm/resource.h" // Files related for detection. #include "scumm/metaengine.h" #include "scumm/detection.h" #include "scumm/detection_tables.h" #include "scumm/file.h" #include "scumm/file_nes.h" namespace Scumm { Common::String ScummEngine::generateFilename(const int room) const { const int diskNumber = (room > 0) ? _res->_types[rtRoom][room]._roomno : 0; Common::String result; if (_game.version == 4) { if (room == 0 || room >= 900) { result = Common::String::format("%03d.lfl", room); } else { result = Common::String::format("disk%02d.lec", diskNumber); } } else { switch (_filenamePattern.genMethod) { case kGenDiskNum: case kGenDiskNumSteam: result = Common::String::format(_filenamePattern.pattern, diskNumber); break; case kGenRoomNum: case kGenRoomNumSteam: result = Common::String::format(_filenamePattern.pattern, room); break; case kGenUnchanged: result = _filenamePattern.pattern; break; default: error("generateFilename: Unsupported genMethod"); } } return result; } Common::String ScummEngine_v60he::generateFilename(const int room) const { Common::String result; char id = 0; switch (_filenamePattern.genMethod) { case kGenHEMac: case kGenHEMacNoParens: case kGenHEPC: if (room < 0) { id = '0' - room; } else { const int diskNumber = (room > 0) ? _res->_types[rtRoom][room]._roomno : 0; id = diskNumber + '0'; } if (_filenamePattern.genMethod == kGenHEPC) { result = Common::String::format("%s.he%c", _filenamePattern.pattern, id); } else { if (id == '3') { // Special case for cursors. // For mac they're stored in game binary. result = _filenamePattern.pattern; } else { if (_filenamePattern.genMethod == kGenHEMac) result = Common::String::format("%s (%c)", _filenamePattern.pattern, id); else result = Common::String::format("%s %c", _filenamePattern.pattern, id); } } break; default: // Fallback to parent method. return ScummEngine::generateFilename(room); } return result; } Common::String ScummEngine_v70he::generateFilename(const int room) const { Common::String result; char id = 0; Common::String bPattern = _filenamePattern.pattern; // Special cases for Blue's games, which share common (b) files. if (_game.id == GID_BIRTHDAYYELLOW || _game.id == GID_BIRTHDAYRED) bPattern = "Blue'sBirthday"; else if (_game.id == GID_TREASUREHUNT) bPattern = "Blue'sTreasureHunt"; switch (_filenamePattern.genMethod) { case kGenHEMac: case kGenHEMacNoParens: case kGenHEPC: case kGenHEIOS: if (_game.heversion >= 98 && room >= 0) { int disk = 0; if (_heV7DiskOffsets) disk = _heV7DiskOffsets[room]; switch (disk) { case 2: id = 'b'; result = bPattern + ".(b)"; break; case 1: id = 'a'; // Some of the newer HE games for iOS use the ".hea" suffix instead. if (_filenamePattern.genMethod == kGenHEIOS) result = Common::String::format("%s.hea", _filenamePattern.pattern); else result = Common::String::format("%s.(a)", _filenamePattern.pattern); break; default: id = '0'; result = Common::String::format("%s.he0", _filenamePattern.pattern); } } else if (room < 0) { id = '0' - room; } else { id = (room == 0) ? '0' : '1'; } if (_filenamePattern.genMethod == kGenHEPC || _filenamePattern.genMethod == kGenHEIOS) { if (id == '3' && _game.id == GID_MOONBASE) { result = Common::String::format("%s.u32", _filenamePattern.pattern); break; } // For HE >= 98, we already called snprintf above. if (_game.heversion < 98 || room < 0) result = Common::String::format("%s.he%c", _filenamePattern.pattern, id); } else { if (id == '3') { // Special case for cursors. // For mac they're stored in game binary. result = _filenamePattern.pattern; } else { Common::String pattern = id == 'b' ? bPattern : _filenamePattern.pattern; if (_filenamePattern.genMethod == kGenHEMac) result = Common::String::format("%s (%c)", pattern.c_str(), id); else result = Common::String::format("%s %c", pattern.c_str(), id); } } break; default: // Fallback to parent method. return ScummEngine_v60he::generateFilename(room); } return result; } bool ScummEngine::isMacM68kIMuse() const { return _game.platform == Common::kPlatformMacintosh && (_game.id == GID_MONKEY2 || _game.id == GID_INDY4) && !(_game.features & GF_MAC_CONTAINER); } } // End of namespace Scumm #pragma mark - #pragma mark --- Detection code --- #pragma mark - // Various methods to help in core detection. #include "scumm/detection_internal.h" #pragma mark - #pragma mark --- Plugin code --- #pragma mark - using namespace Scumm; const char *ScummMetaEngine::getName() const { return "scumm"; } bool ScummMetaEngine::hasFeature(MetaEngineFeature f) const { return (f == kSupportsListSaves) || (f == kSupportsLoadingDuringStartup) || (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || (f == kSavesSupportPlayTime) || (f == kSimpleSavesNames); } bool ScummEngine::hasFeature(EngineFeature f) const { return (f == kSupportsReturnToLauncher) || (f == kSupportsLoadingDuringRuntime) || (f == kSupportsSavingDuringRuntime) || (f == kSupportsSubtitleOptions); } /** * Create a ScummEngine instance, based on the given detector data. * * This is heavily based on our MD5 detection scheme. */ Common::Error ScummMetaEngine::createInstance(OSystem *syst, Engine **engine) const { assert(syst); assert(engine); const char *gameid = ConfMan.get("gameid").c_str(); // We start by checking whether the specified game ID is obsolete. // If that is the case, we automatically upgrade the target to use // the correct new game ID (and platform, if specified). Engines::upgradeTargetIfNecessary(obsoleteGameIDsTable); // Fetch the list of files in the current directory. Common::FSList fslist; Common::FSNode dir(ConfMan.get("path")); if (!dir.isDirectory()) return Common::kPathNotDirectory; if (!dir.getChildren(fslist, Common::FSNode::kListAll)) return Common::kNoGameDataFoundError; // Invoke the detector, but fixed to the specified gameid. Common::List results; ::detectGames(fslist, results, gameid); // Unable to locate game data. if (results.empty()) return Common::kNoGameDataFoundError; // No unique match found. If a platform override is present, try to // narrow down the list a bit more. if (results.size() > 1 && ConfMan.hasKey("platform")) { Common::Platform platform = Common::parsePlatform(ConfMan.get("platform")); Common::List tmp; // Copy only those candidates which match the platform setting. for (Common::List::iterator x = results.begin(); x != results.end(); ++x) { if (x->game.platform == platform) { tmp.push_back(*x); } } // If we narrowed it down too much, print a warning, else use the list // we just computed as new candidates list. if (tmp.empty()) { warning("Engine_SCUMM_create: Game data inconsistent with platform override"); } else { results = tmp; } } // Still no unique match found -> print a warning. if (results.size() > 1) warning("Engine_SCUMM_create: No unique game candidate found, using first one"); // Simply use the first match. DetectorResult res(*(results.begin())); debug(1, "Using gameid %s, variant %s, extra %s", res.game.gameid, res.game.variant, res.extra); debug(1, " SCUMM version %d, HE version %d", res.game.version, res.game.heversion); // Print the MD5 of the game; either verbose using printf, in case of an // unknown MD5, or with a medium debug level in case of a known MD5 (for // debugging purposes). if (!findInMD5Table(res.md5.c_str())) { Common::String md5Warning; md5Warning = ("Your game version appears to be unknown. If this is *NOT* a fan-modified\n" "version (in particular, not a fan-made translation), please, report the\n" "following data to the ScummVM team along with the name of the game you tried\n" "to add and its version, language, etc.:\n"); md5Warning += Common::String::format(" SCUMM gameid '%s', file '%s', MD5 '%s'\n\n", res.game.gameid, generateFilenameForDetection(res.fp.pattern, res.fp.genMethod, res.game.platform).c_str(), res.md5.c_str()); g_system->logMessage(LogMessageType::kWarning, md5Warning.c_str()); } else { debug(1, "Using MD5 '%s'", res.md5.c_str()); } // We don't support the "Lite" version off puttzoo iOS because it contains // the full game. if (!strcmp(res.game.gameid, "puttzoo") && !strcmp(res.extra, "Lite")) { GUIErrorMessage(_("The Lite version of Putt-Putt Saves the Zoo iOS is not supported to avoid piracy.\n" "The full version is available for purchase from the iTunes Store.")); return Common::kUnsupportedGameidError; } // If the GUI options were updated, we catch this here and update them in the users config // file transparently. Common::updateGameGUIOptions(res.game.guioptions, getGameGUIOptionsDescriptionLanguage(res.language)); // Check for a user override of the platform. We allow the user to override // the platform, to make it possible to add games which are not yet in // our MD5 database but require a specific platform setting. // TODO: Do we really still need/want the platform override? if (ConfMan.hasKey("platform")) res.game.platform = Common::parsePlatform(ConfMan.get("platform")); // Language override. if (ConfMan.hasKey("language")) res.language = Common::parseLanguage(ConfMan.get("language")); // V3 FM-TOWNS games *always* should use the corresponding music driver, // anything else makes no sense for them. // TODO: Maybe allow the null driver, too? if (res.game.platform == Common::kPlatformFMTowns && res.game.version == 3) res.game.midi = MDT_TOWNS; // Finally, we have massaged the GameDescriptor to our satisfaction, and can // instantiate the appropriate game engine. Hooray! switch (res.game.version) { case 0: *engine = new ScummEngine_v0(syst, res); break; case 1: case 2: *engine = new ScummEngine_v2(syst, res); break; case 3: if (res.game.features & GF_OLD256) *engine = new ScummEngine_v3(syst, res); else *engine = new ScummEngine_v3old(syst, res); break; case 4: *engine = new ScummEngine_v4(syst, res); break; case 5: *engine = new ScummEngine_v5(syst, res); break; case 6: switch (res.game.heversion) { #ifdef ENABLE_HE case 200: *engine = new ScummEngine_vCUPhe(syst, res); break; case 101: case 100: *engine = new ScummEngine_v100he(syst, res); break; case 99: *engine = new ScummEngine_v99he(syst, res); break; case 98: case 95: case 90: *engine = new ScummEngine_v90he(syst, res); break; case 85: case 80: *engine = new ScummEngine_v80he(syst, res); break; case 74: case 73: case 72: *engine = new ScummEngine_v72he(syst, res); break; case 71: *engine = new ScummEngine_v71he(syst, res); break; #else case 200: case 101: case 100: case 99: case 98: case 95: case 90: case 85: case 80: case 74: case 73: case 72: case 71: return Common::Error(Common::kUnsupportedGameidError, _s("HE v71+ support is not compiled in")); #endif case 70: *engine = new ScummEngine_v70he(syst, res); break; case 62: case 61: case 60: *engine = new ScummEngine_v60he(syst, res); break; default: *engine = new ScummEngine_v6(syst, res); } break; #ifdef ENABLE_SCUMM_7_8 case 7: *engine = new ScummEngine_v7(syst, res); break; case 8: *engine = new ScummEngine_v8(syst, res); break; #else case 7: case 8: return Common::Error(Common::kUnsupportedGameidError, _s("SCUMM v7-8 support is not compiled in")); #endif default: return Common::kUnsupportedGameidError; } return Common::kNoError; } int ScummMetaEngine::getMaximumSaveSlot() const { return 99; } namespace Scumm { bool getSavegameName(Common::InSaveFile *in, Common::String &desc, int heversion); } // End of namespace Scumm SaveStateList ScummMetaEngine::listSaves(const char *target) const { Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); Common::StringArray filenames; Common::String saveDesc; Common::String pattern = target; pattern += ".s##"; filenames = saveFileMan->listSavefiles(pattern); SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { // Obtain the last 2 digits of the filename, since they correspond to the save slot. int slotNum = atoi(file->c_str() + file->size() - 2); if (slotNum >= 0 && slotNum <= 99) { Common::InSaveFile *in = saveFileMan->openForLoading(*file); if (in) { Scumm::getSavegameName(in, saveDesc, 0); // FIXME: heversion?!? saveList.push_back(SaveStateDescriptor(slotNum, saveDesc)); delete in; } } } // Sort saves based on slot number. Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } void ScummMetaEngine::removeSaveState(const char *target, int slot) const { Common::String filename = ScummEngine::makeSavegameName(target, slot, false); g_system->getSavefileManager()->removeSavefile(filename); } SaveStateDescriptor ScummMetaEngine::querySaveMetaInfos(const char *target, int slot) const { Common::String saveDesc; Graphics::Surface *thumbnail = nullptr; SaveStateMetaInfos infos; memset(&infos, 0, sizeof(infos)); SaveStateMetaInfos *infoPtr = &infos; // FIXME: heversion?!? if (!ScummEngine::querySaveMetaInfos(target, slot, 0, saveDesc, thumbnail, infoPtr)) { return SaveStateDescriptor(); } SaveStateDescriptor desc(slot, saveDesc); // Do not allow save slot 0 (used for auto-saving) to be deleted or // overwritten. if (slot == 0) { desc.setWriteProtectedFlag(true); desc.setDeletableFlag(false); } desc.setThumbnail(thumbnail); if (infoPtr) { int day = (infos.date >> 24) & 0xFF; int month = (infos.date >> 16) & 0xFF; int year = infos.date & 0xFFFF; desc.setSaveDate(year, month, day); int hour = (infos.time >> 8) & 0xFF; int minutes = infos.time & 0xFF; desc.setSaveTime(hour, minutes); desc.setPlayTime(infos.playtime * 1000); } return desc; } #if PLUGIN_ENABLED_DYNAMIC(SCUMM) REGISTER_PLUGIN_DYNAMIC(SCUMM, PLUGIN_TYPE_ENGINE, ScummMetaEngine); #else REGISTER_PLUGIN_STATIC(SCUMM, PLUGIN_TYPE_ENGINE, ScummMetaEngine); #endif