1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "engines/metaengine.h"
24 
25 #include "backends/keymapper/action.h"
26 #include "backends/keymapper/keymap.h"
27 #include "backends/keymapper/standard-actions.h"
28 
29 #include "common/savefile.h"
30 #include "common/system.h"
31 #include "common/translation.h"
32 
33 #include "engines/dialogs.h"
34 
35 #include "graphics/palette.h"
36 #include "graphics/scaler.h"
37 #include "graphics/managed_surface.h"
38 #include "graphics/thumbnail.h"
39 
getSavegameFile(int saveGameIdx,const char * target) const40 Common::String MetaEngine::getSavegameFile(int saveGameIdx, const char *target) const {
41 	if (!target)
42 		target = getEngineId();
43 	if (saveGameIdx == kSavegameFilePattern) {
44 		// Pattern requested
45 		const char *pattern = hasFeature(kSimpleSavesNames) ? "%s.###" : "%s.s##";
46 		return Common::String::format(pattern, target);
47 	} else {
48 		// Specific filename requested
49 		const char *pattern = hasFeature(kSimpleSavesNames) ? "%s.%03d" : "%s.s%02d";
50 		return Common::String::format(pattern, target, saveGameIdx);
51 	}
52 }
53 
initKeymaps(const char * target) const54 Common::KeymapArray MetaEngine::initKeymaps(const char *target) const {
55 	using namespace Common;
56 
57 	Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "engine-default", _("Default game keymap"));
58 
59 	Action *act;
60 
61 	act = new Action(kStandardActionLeftClick, _("Left Click"));
62 	act->setLeftClickEvent();
63 	act->addDefaultInputMapping("MOUSE_LEFT");
64 	act->addDefaultInputMapping("JOY_A");
65 	engineKeyMap->addAction(act);
66 
67 	act = new Action(kStandardActionMiddleClick, _("Middle Click"));
68 	act->addDefaultInputMapping("MOUSE_MIDDLE");
69 	act->setMiddleClickEvent();
70 	engineKeyMap->addAction(act);
71 
72 	act = new Action(kStandardActionRightClick, _("Right Click"));
73 	act->setRightClickEvent();
74 	act->addDefaultInputMapping("MOUSE_RIGHT");
75 	act->addDefaultInputMapping("JOY_B");
76 	engineKeyMap->addAction(act);
77 
78 	act = new Action(kStandardActionPause, _("Pause"));
79 	act->setKeyEvent(KeyState(KEYCODE_SPACE, ' '));
80 	act->addDefaultInputMapping("SPACE");
81 	engineKeyMap->addAction(act);
82 
83 	act = new Action(kStandardActionOpenMainMenu, _("Game menu"));
84 	act->setKeyEvent(KeyState(KEYCODE_F5, ASCII_F5));
85 	act->addDefaultInputMapping("F5");
86 	act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
87 	engineKeyMap->addAction(act);
88 
89 	act = new Action(kStandardActionSkip, _("Skip"));
90 	act->setKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE));
91 	act->addDefaultInputMapping("ESCAPE");
92 	act->addDefaultInputMapping("JOY_Y");
93 	engineKeyMap->addAction(act);
94 
95 	act = new Action("SKLI", _("Skip line"));
96 	act->setKeyEvent(KeyState(KEYCODE_PERIOD, '.'));
97 	act->addDefaultInputMapping("PERIOD");
98 	act->addDefaultInputMapping("JOY_X");
99 	engineKeyMap->addAction(act);
100 
101 	act = new Action("PIND", _("Predictive input dialog"));
102 	act->setEvent(EVENT_PREDICTIVE_DIALOG);
103 	engineKeyMap->addAction(act);
104 
105 	act = new Action("RETURN", _("Confirm"));
106 	act->setKeyEvent(KeyState(KEYCODE_RETURN, ASCII_RETURN));
107 	act->addDefaultInputMapping("RETURN");
108 	engineKeyMap->addAction(act);
109 
110 	act = new Action(kStandardActionMoveUp, _("Up"));
111 	act->setKeyEvent(KEYCODE_KP8);
112 	act->addDefaultInputMapping("JOY_UP");
113 	engineKeyMap->addAction(act);
114 
115 	act = new Action(kStandardActionMoveDown, _("Down"));
116 	act->setKeyEvent(KEYCODE_KP2);
117 	act->addDefaultInputMapping("JOY_DOWN");
118 	engineKeyMap->addAction(act);
119 
120 	act = new Action(kStandardActionMoveLeft, _("Left"));
121 	act->setKeyEvent(KEYCODE_KP4);
122 	act->addDefaultInputMapping("JOY_LEFT");
123 	engineKeyMap->addAction(act);
124 
125 	act = new Action(kStandardActionMoveRight, _("Right"));
126 	act->setKeyEvent(KEYCODE_KP6);
127 	act->addDefaultInputMapping("JOY_RIGHT");
128 	engineKeyMap->addAction(act);
129 
130 	return Keymap::arrayOf(engineKeyMap);
131 }
132 
getAchievementsInfo(const Common::String & target) const133 const Common::AchievementsInfo MetaEngine::getAchievementsInfo(const Common::String &target) const {
134 	const Common::AchievementDescriptionList* achievementDescriptionList = getAchievementDescriptionList();
135 	if (achievementDescriptionList == nullptr) {
136 		return Common::AchievementsInfo();
137 	}
138 
139 	Common::String gameId = ConfMan.get("gameid", target);
140 
141 	Common::AchievementsPlatform platform = Common::UNK_ACHIEVEMENTS;
142 	Common::String extra = ConfMan.get("extra", target);
143 	if (extra.contains("GOG")) {
144 		platform = Common::GALAXY_ACHIEVEMENTS;
145 	} else if (extra.contains("Steam")) {
146 		platform = Common::STEAM_ACHIEVEMENTS;
147 	}
148 
149 	// "(gameId, platform) -> result" search
150 	Common::AchievementsInfo result;
151 	for (const Common::AchievementDescriptionList *i = achievementDescriptionList; i->gameId; i++) {
152 		if (i->gameId == gameId && i->platform == platform) {
153 			result.platform = i->platform;
154 			result.appId = i->appId;
155 			break;
156 		}
157 	}
158 	return result;
159 }
160 
hasFeature(MetaEngineFeature f) const161 bool MetaEngine::hasFeature(MetaEngineFeature f) const {
162 	return
163 		(f == kSupportsListSaves) ||
164 		(f == kSupportsDeleteSave) ||
165 		(f == kSavesSupportMetaInfo) ||
166 		(f == kSavesSupportThumbnail) ||
167 		(f == kSavesSupportCreationDate) ||
168 		(f == kSavesSupportPlayTime) ||
169 		(f == kSupportsLoadingDuringStartup) ||
170 		(f == kSimpleSavesNames) ||
171 		(f == kSavesUseExtendedFormat);
172 }
173 
appendExtendedSave(Common::OutSaveFile * saveFile,uint32 playtime,Common::String desc,bool isAutosave)174 void MetaEngine::appendExtendedSave(Common::OutSaveFile *saveFile, uint32 playtime,
175 		Common::String desc, bool isAutosave) {
176 	appendExtendedSaveToStream(saveFile, playtime, desc, isAutosave);
177 
178 	saveFile->finalize();
179 }
180 
appendExtendedSaveToStream(Common::WriteStream * saveFile,uint32 playtime,Common::String desc,bool isAutosave,uint32 posoffset)181 void MetaEngine::appendExtendedSaveToStream(Common::WriteStream *saveFile, uint32 playtime,
182 		Common::String desc, bool isAutosave, uint32 posoffset) {
183 	ExtendedSavegameHeader header;
184 
185 	uint headerPos = saveFile->pos() + posoffset;
186 
187 	strcpy(header.id, "SVMCR");
188 	header.version = EXTENDED_SAVE_VERSION;
189 
190 	TimeDate curTime;
191 	g_system->getTimeAndDate(curTime);
192 
193 	header.date = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
194 	header.time = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
195 
196 	saveFile->write(header.id, 6);
197 	saveFile->writeByte(header.version);
198 	saveFile->writeUint32LE(header.date);
199 	saveFile->writeUint16LE(header.time);
200 	saveFile->writeUint32LE(playtime);
201 
202 	saveFile->writeByte(desc.size());
203 	saveFile->writeString(desc);
204 	saveFile->writeByte(isAutosave);
205 
206 	// Write out the thumbnail
207 	Graphics::Surface thumb;
208 	getSavegameThumbnail(thumb);
209 	Graphics::saveThumbnail(*saveFile, thumb);
210 	thumb.free();
211 
212 	saveFile->writeUint32LE(headerPos);	// Store where the header starts
213 }
214 
copySaveFileToFreeSlot(const char * target,int slot)215 bool MetaEngine::copySaveFileToFreeSlot(const char *target, int slot)
216 {
217 	const int emptySlot = findEmptySaveSlot(target);
218 	if (emptySlot == -1)
219 		return false;
220 	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
221 	return saveFileMan->copySavefile(getSavegameFile(slot, target), getSavegameFile(emptySlot, target));
222 }
223 
getSavegameThumbnail(Graphics::Surface & thumb)224 void MetaEngine::getSavegameThumbnail(Graphics::Surface &thumb) {
225 	::createThumbnailFromScreen(&thumb);
226 }
227 
parseSavegameHeader(ExtendedSavegameHeader * header,SaveStateDescriptor * desc)228 void MetaEngine::parseSavegameHeader(ExtendedSavegameHeader *header, SaveStateDescriptor *desc) {
229 	int day = (header->date >> 24) & 0xFF;
230 	int month = (header->date >> 16) & 0xFF;
231 	int year = header->date & 0xFFFF;
232 	desc->setSaveDate(year, month, day);
233 	int hour = (header->time >> 8) & 0xFF;
234 	int minutes = header->time & 0xFF;
235 	desc->setSaveTime(hour, minutes);
236 	desc->setPlayTime(header->playtime * 1000);
237 
238 	desc->setDescription(header->description);
239 }
240 
fillDummyHeader(ExtendedSavegameHeader * header)241 void MetaEngine::fillDummyHeader(ExtendedSavegameHeader *header) {
242 	// This is wrong header, perhaps it is original savegame. Thus fill out dummy values
243 	header->date = (20 << 24) | (9 << 16) | 2016;
244 	header->time = (9 << 8) | 56;
245 	header->playtime = 0;
246 }
247 
readSavegameHeader(Common::InSaveFile * in,ExtendedSavegameHeader * header,bool skipThumbnail)248 WARN_UNUSED_RESULT bool MetaEngine::readSavegameHeader(Common::InSaveFile *in, ExtendedSavegameHeader *header, bool skipThumbnail) {
249 	uint oldPos = in->pos();
250 
251 	in->seek(-4, SEEK_END);
252 
253 	int headerOffset = in->readUint32LE();
254 
255 	// Sanity check
256 	if (headerOffset >= in->pos() || headerOffset == 0) {
257 		in->seek(oldPos, SEEK_SET); // Rewind the file
258 		fillDummyHeader(header);
259 		return false;
260 	}
261 
262 	in->seek(headerOffset, SEEK_SET);
263 
264 	in->read(header->id, 6);
265 
266 	// Validate the header Id
267 	if (strcmp(header->id, "SVMCR")) {
268 		in->seek(oldPos, SEEK_SET); // Rewind the file
269 		fillDummyHeader(header);
270 		return false;
271 	}
272 
273 	header->version = in->readByte();
274 	header->date = in->readUint32LE();
275 	header->time = in->readUint16LE();
276 	header->playtime = in->readUint32LE();
277 
278 	if (header->version > 1)
279 		header->description = in->readPascalString();
280 
281 	// Generate savename
282 	SaveStateDescriptor desc;
283 
284 	parseSavegameHeader(header, &desc);
285 
286 	header->saveName = Common::String::format("%s %s", desc.getSaveDate().c_str(), desc.getSaveTime().c_str());
287 
288 	if (header->description.empty())
289 		header->description = header->saveName;
290 
291 	// Get the flag for whether it's an autosave
292 	header->isAutosave = (header->version >= 4) ? in->readByte() : false;
293 
294 	// Get the thumbnail
295 	if (!Graphics::loadThumbnail(*in, header->thumbnail, skipThumbnail)) {
296 		in->seek(oldPos, SEEK_SET); // Rewind the file
297 		return false;
298 	}
299 
300 	in->seek(oldPos, SEEK_SET); // Rewind the file
301 
302 	return true;
303 }
304 
305 
306 //////////////////////////////////////////////
307 // MetaEngine default implementations
308 //////////////////////////////////////////////
309 
findEmptySaveSlot(const char * target)310 int MetaEngine::findEmptySaveSlot(const char *target) {
311 	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
312 	const int maxSaveSlot = getMaximumSaveSlot();
313 	const int autosaveSlot = getAutosaveSlot();
314 	for (int slot = 0; slot <= maxSaveSlot; ++slot) {
315 		if (slot == autosaveSlot)
316 			continue;
317 		const Common::String filename = getSavegameFile(slot, target);
318 		if (!saveFileMan->exists(filename))
319 			return slot;
320 	}
321 	return -1;
322 }
323 
listSaves(const char * target) const324 SaveStateList MetaEngine::listSaves(const char *target) const {
325 	if (!hasFeature(kSavesUseExtendedFormat))
326 		return SaveStateList();
327 
328 	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
329 	Common::StringArray filenames;
330 	Common::String pattern(getSavegameFilePattern(target));
331 
332 	filenames = saveFileMan->listSavefiles(pattern);
333 
334 	SaveStateList saveList;
335 	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
336 		// Obtain the last 2 digits of the filename, since they correspond to the save slot
337 		int slotNum = atoi(file->c_str() + file->size() - 2);
338 
339 		if (slotNum >= 0 && slotNum <= getMaximumSaveSlot()) {
340 			SaveStateDescriptor desc = querySaveMetaInfos(target, slotNum);
341 			if (desc.getSaveSlot() != -1) {
342 				saveList.push_back(desc);
343 			}
344 		}
345 	}
346 
347 	// Sort saves based on slot number.
348 	Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
349 	return saveList;
350 }
351 
listSaves(const char * target,bool saveMode) const352 SaveStateList MetaEngine::listSaves(const char *target, bool saveMode) const {
353 	SaveStateList saveList = listSaves(target);
354 	int autosaveSlot = ConfMan.getInt("autosave_period") ? getAutosaveSlot() : -1;
355 	if (!saveMode || autosaveSlot == -1)
356 		return saveList;
357 
358 	// Check to see if an autosave is present
359 	for (SaveStateList::iterator it = saveList.begin(); it != saveList.end(); ++it) {
360 		// It has an autosave
361 		if (it->isAutosave())
362 			return saveList;
363 	}
364 
365 	// No autosave yet. We want to add a dummy one in so that it can be marked as
366 	// write protected, and thus be prevented from being saved in
367 	SaveStateDescriptor desc(this, autosaveSlot, _("Autosave"));
368 	saveList.push_back(desc);
369 	Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
370 
371 	return saveList;
372 }
373 
registerDefaultSettings(const Common::String &) const374 void MetaEngineDetection::registerDefaultSettings(const Common::String &) const {
375 	// Note that as we don't pass the target to getExtraGuiOptions
376 	//  we get all the options, even those not relevant for the current
377 	//  game. This is necessary because some engines unconditionally
378 	//  access the configuration.
379 	const ExtraGuiOptions engineOptions = getExtraGuiOptions("");
380 	for (uint i = 0; i < engineOptions.size(); i++) {
381 		ConfMan.registerDefault(engineOptions[i].configOption, engineOptions[i].defaultState);
382 	}
383 }
384 
buildEngineOptionsWidgetStatic(GUI::GuiObject * boss,const Common::String & name,const Common::String & target) const385 GUI::OptionsContainerWidget *MetaEngineDetection::buildEngineOptionsWidgetStatic(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const {
386 	const ExtraGuiOptions engineOptions = getExtraGuiOptions(target);
387 	if (engineOptions.empty()) {
388 		return nullptr;
389 	}
390 
391 	return new GUI::ExtraGuiOptionsWidget(boss, name, target, engineOptions);
392 }
393 
removeSaveState(const char * target,int slot) const394 void MetaEngine::removeSaveState(const char *target, int slot) const {
395 	if (!hasFeature(kSavesUseExtendedFormat))
396 		return;
397 
398 	g_system->getSavefileManager()->removeSavefile(getSavegameFile(slot, target));
399 }
400 
querySaveMetaInfos(const char * target,int slot) const401 SaveStateDescriptor MetaEngine::querySaveMetaInfos(const char *target, int slot) const {
402 	if (!hasFeature(kSavesUseExtendedFormat))
403 		return SaveStateDescriptor();
404 
405 	Common::ScopedPtr<Common::InSaveFile> f(g_system->getSavefileManager()->openForLoading(
406 		getSavegameFile(slot, target)));
407 
408 	if (f) {
409 		ExtendedSavegameHeader header;
410 		if (!readSavegameHeader(f.get(), &header, false)) {
411 			return SaveStateDescriptor();
412 		}
413 
414 		// Create the return descriptor
415 		SaveStateDescriptor desc(this, slot, Common::U32String());
416 		parseSavegameHeader(&header, &desc);
417 		desc.setThumbnail(header.thumbnail);
418 		return desc;
419 	}
420 
421 	return SaveStateDescriptor();
422 }
423