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 "base/plugins.h"
24 
25 #include "engines/advancedDetector.h"
26 
27 #include "common/system.h"
28 #include "common/textconsole.h"
29 #include "common/translation.h"
30 #include "common/util.h"
31 
32 #include "cine/cine.h"
33 #include "cine/various.h"
34 
35 #include "cine/detection.h"
36 
37 namespace Cine {
38 
39 #define MAX_SAVEGAMES (ARRAYSIZE(Cine::currentSaveName))
40 #define SAVEGAME_NAME_LEN (sizeof(Cine::currentSaveName[0]))
41 #define SAVELIST_SIZE (MAX_SAVEGAMES * SAVEGAME_NAME_LEN)
42 
mayHave256Colors() const43 bool CineEngine::mayHave256Colors() const { return getGameType() == Cine::GType_OS && getPlatform() == Common::kPlatformDOS; }
getGameType() const44 int CineEngine::getGameType() const { return _gameDescription->gameType; }
getFeatures() const45 uint32 CineEngine::getFeatures() const { return _gameDescription->features; }
getLanguage() const46 Common::Language CineEngine::getLanguage() const { return _gameDescription->desc.language; }
getPlatform() const47 Common::Platform CineEngine::getPlatform() const { return _gameDescription->desc.platform; }
48 
49 } // End of namespace Cine
50 
51 class CineMetaEngine : public AdvancedMetaEngine {
52 public:
getName() const53 	const char *getName() const override {
54 		return "cine";
55 	}
56 
57 	Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
58 
59 	bool hasFeature(MetaEngineFeature f) const override;
60 	SaveStateList listSaves(const char *target) const override;
61 	int getMaximumSaveSlot() const override;
62 	void removeSaveState(const char *target, int slot) const override;
63 	Common::String getSavegameFile(int saveGameIdx, const char *target = nullptr) const override;
64 	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
65 };
66 
hasFeature(MetaEngineFeature f) const67 bool CineMetaEngine::hasFeature(MetaEngineFeature f) const {
68 	return
69 		(f == kSupportsListSaves) ||
70 		(f == kSupportsLoadingDuringStartup) ||
71 		(f == kSupportsDeleteSave) ||
72 		(f == kSavesSupportMetaInfo) ||
73 		(f == kSavesSupportThumbnail) ||
74 		(f == kSavesSupportCreationDate) ||
75 		(f == kSavesSupportPlayTime) ||
76 		(f == kSavesUseExtendedFormat);
77 }
78 
hasFeature(EngineFeature f) const79 bool Cine::CineEngine::hasFeature(EngineFeature f) const {
80 	return
81 		(f == kSupportsReturnToLauncher) ||
82 		(f == kSupportsLoadingDuringRuntime) ||
83 		(f == kSupportsSavingDuringRuntime);
84 }
85 
createInstance(OSystem * syst,Engine ** engine,const ADGameDescription * desc) const86 Common::Error CineMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
87 	*engine = new Cine::CineEngine(syst, (const Cine::CINEGameDescription *)desc);
88 	return Common::kNoError;
89 }
90 
listSaves(const char * target) const91 SaveStateList CineMetaEngine::listSaves(const char *target) const {
92 	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
93 	SaveStateList saveList;
94 
95 	Common::String pattern;
96 
97 	Common::StringArray::const_iterator file;
98 
99 	Common::String filename = target;
100 	filename += ".dir";
101 	Common::InSaveFile *in = saveFileMan->openForLoading(filename);
102 	bool foundAutosave = false;
103 	if (in) {
104 		typedef char CommandeType[SAVEGAME_NAME_LEN];
105 		CommandeType saveNames[MAX_SAVEGAMES];
106 
107 		// Initialize all savegames' descriptions to empty strings
108 		// so that if the savegames' descriptions can only be partially read from file
109 		// then the missing ones are correctly set to empty strings.
110 		memset(saveNames, 0, sizeof(saveNames));
111 
112 		in->read(saveNames, SAVELIST_SIZE);
113 		CommandeType saveDesc;
114 
115 		pattern = target;
116 		pattern += ".#*";
117 		Common::StringArray filenames = saveFileMan->listSavefiles(pattern);
118 
119 		for (file = filenames.begin(); file != filenames.end(); ++file) {
120 			// Obtain the extension part of the filename, since it corresponds to the save slot number
121 			Common::String ext = Common::lastPathComponent(*file, '.');
122 			int slotNum = (int)ext.asUint64();
123 
124 			if (ext.equals(Common::String::format("%d", slotNum)) &&
125 				slotNum >= 0 && slotNum < MAX_SAVEGAMES) {
126 				// Copy the savegame description making sure it ends with a trailing zero
127 				strncpy(saveDesc, saveNames[slotNum], SAVEGAME_NAME_LEN);
128 				saveDesc[sizeof(CommandeType) - 1] = 0;
129 
130 				SaveStateDescriptor saveStateDesc(this, slotNum, saveDesc);
131 
132 				if (saveStateDesc.getDescription().empty()) {
133 					if (saveStateDesc.isAutosave()) {
134 						saveStateDesc.setDescription(_("Unnamed autosave"));
135 					} else {
136 						saveStateDesc.setDescription(_("Unnamed savegame"));
137 					}
138 				}
139 
140 				if (saveStateDesc.isAutosave()) {
141 					foundAutosave = true;
142 				}
143 
144 				saveList.push_back(saveStateDesc);
145 			}
146 		}
147 	}
148 
149 	delete in;
150 
151 	// No saving on empty autosave slot
152 	if (!foundAutosave) {
153 		SaveStateDescriptor desc(this, getAutosaveSlot(), _("Empty autosave"));
154 		saveList.push_back(desc);
155 	}
156 
157 	// Sort saves based on slot number.
158 	Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
159 	return saveList;
160 }
161 
getMaximumSaveSlot() const162 int CineMetaEngine::getMaximumSaveSlot() const { return MAX_SAVEGAMES - 1; }
163 
getSavegameFile(int saveGameIdx,const char * target) const164 Common::String CineMetaEngine::getSavegameFile(int saveGameIdx, const char *target) const {
165 	return Common::String::format("%s.%d", target == nullptr ? getEngineId() : target, saveGameIdx);
166 }
167 
querySaveMetaInfos(const char * target,int slot) const168 SaveStateDescriptor CineMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
169 	if (slot < 0 || slot > getMaximumSaveSlot()) {
170 		// HACK: Try to make SaveLoadChooserGrid::open() not use save slot
171 		// numbers over the maximum save slot number for "New save".
172 		SaveStateDescriptor desc;
173 		desc.setWriteProtectedFlag(true);
174 		return desc;
175 	}
176 
177 	Common::ScopedPtr<Common::InSaveFile> f(g_system->getSavefileManager()->openForLoading(
178 		getSavegameFile(slot, target)));
179 
180 	if (f) {
181 		// Create the return descriptor
182 		SaveStateDescriptor desc(this, slot, Common::U32String());
183 
184 		ExtendedSavegameHeader header;
185 		if (readSavegameHeader(f.get(), &header, false)) {
186 			parseSavegameHeader(&header, &desc);
187 			desc.setThumbnail(header.thumbnail);
188 		} else {
189 			// Load savegame descriptions from index file
190 			typedef char CommandeType[SAVEGAME_NAME_LEN];
191 			CommandeType saveNames[MAX_SAVEGAMES];
192 			memset(saveNames, 0, sizeof(saveNames));
193 
194 			Common::InSaveFile *in;
195 			in = g_system->getSavefileManager()->openForLoading(Common::String::format("%s.dir", target));
196 
197 			if (in) {
198 				in->read(saveNames, SAVELIST_SIZE);
199 				delete in;
200 			}
201 
202 			saveNames[slot][SAVEGAME_NAME_LEN - 1] = 0;
203 			Common::String saveNameStr((const char *)saveNames[slot]);
204 			desc.setDescription(saveNameStr);
205 		}
206 
207 		if (desc.getDescription().empty()) {
208 			desc.setDescription(_("Unnamed savegame"));
209 		}
210 
211 		return desc;
212 	}
213 
214 	// No saving on empty autosave slot
215 	if (slot == getAutosaveSlot()) {
216 		return SaveStateDescriptor(this, slot, _("Empty autosave"));
217 	}
218 
219 	return SaveStateDescriptor();
220 }
221 
removeSaveState(const char * target,int slot) const222 void CineMetaEngine::removeSaveState(const char *target, int slot) const {
223 	if (slot < 0 || slot >= MAX_SAVEGAMES) {
224 		return;
225 	}
226 
227 	// Load savegame descriptions from index file
228 	typedef char CommandeType[SAVEGAME_NAME_LEN];
229 	CommandeType saveNames[MAX_SAVEGAMES];
230 
231 	// Initialize all savegames' descriptions to empty strings
232 	// so that if the savegames' descriptions can only be partially read from file
233 	// then the missing ones are correctly set to empty strings.
234 	memset(saveNames, 0, sizeof(saveNames));
235 
236 	Common::InSaveFile *in;
237 	in = g_system->getSavefileManager()->openForLoading(Common::String::format("%s.dir", target));
238 
239 	if (!in)
240 		return;
241 
242 	in->read(saveNames, SAVELIST_SIZE);
243 	delete in;
244 
245 	// Set description for selected slot
246 	char slotName[SAVEGAME_NAME_LEN];
247 	slotName[0] = 0;
248 	Common::strlcpy(saveNames[slot], slotName, SAVEGAME_NAME_LEN);
249 
250 	// Update savegame descriptions
251 	Common::String indexFile = Common::String::format("%s.dir", target);
252 	Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(indexFile);
253 	if (!out) {
254 		warning("Unable to open file %s for saving", indexFile.c_str());
255 		return;
256 	}
257 
258 	out->write(saveNames, SAVELIST_SIZE);
259 	delete out;
260 
261 	// Delete save file
262 	Common::String saveFileName = getSavegameFile(slot, target);
263 
264 	g_system->getSavefileManager()->removeSavefile(saveFileName);
265 }
266 
267 #if PLUGIN_ENABLED_DYNAMIC(CINE)
268 	REGISTER_PLUGIN_DYNAMIC(CINE, PLUGIN_TYPE_ENGINE, CineMetaEngine);
269 #else
270 	REGISTER_PLUGIN_STATIC(CINE, PLUGIN_TYPE_ENGINE, CineMetaEngine);
271 #endif
272 
273 namespace Cine {
274 
loadGameState(int slot)275 Common::Error CineEngine::loadGameState(int slot) {
276 	bool gameLoaded = makeLoad(getSaveStateName(slot));
277 
278 	return gameLoaded ? Common::kNoError : Common::kUnknownError;
279 }
280 
saveGameState(int slot,const Common::String & desc,bool isAutosave)281 Common::Error CineEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
282 	if (slot < 0 || slot >= MAX_SAVEGAMES) {
283 		return Common::kCreatingFileFailed;
284 	}
285 
286 	// Load savegame descriptions from index file
287 	loadSaveDirectory();
288 
289 	// Set description for selected slot making sure it ends with a trailing zero
290 	strncpy(currentSaveName[slot], desc.c_str(), sizeof(CommandeType));
291 	currentSaveName[slot][sizeof(CommandeType) - 1] = 0;
292 
293 	// Update savegame descriptions
294 	Common::String indexFile = _targetName + ".dir";
295 
296 	Common::OutSaveFile *fHandle = _saveFileMan->openForSaving(indexFile);
297 	if (!fHandle) {
298 		warning("Unable to open file %s for saving", indexFile.c_str());
299 		return Common::kUnknownError;
300 	}
301 
302 	fHandle->write(currentSaveName, SAVELIST_SIZE);
303 	delete fHandle;
304 
305 	// Save game
306 	makeSave(getSaveStateName(slot), getTotalPlayTime() / 1000, desc, isAutosave);
307 
308 	checkDataDisk(-1);
309 
310 	return Common::kNoError;
311 }
312 
getSaveStateName(int slot) const313 Common::String CineEngine::getSaveStateName(int slot) const {
314 	return getMetaEngine()->getSavegameFile(slot, _targetName.c_str());
315 }
316 
canLoadGameStateCurrently()317 bool CineEngine::canLoadGameStateCurrently() {
318 	return (!disableSystemMenu && !inMenu);
319 }
320 
canSaveGameStateCurrently()321 bool CineEngine::canSaveGameStateCurrently() {
322 	return (allowPlayerInput && !disableSystemMenu && !inMenu);
323 }
324 
325 } // End of namespace Cine
326 
327