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