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 "sky/control.h"
24 #include "sky/sky.h"
25 
26 #include "base/plugins.h"
27 
28 #include "common/config-manager.h"
29 #include "engines/advancedDetector.h"
30 #include "engines/metaengine.h"
31 #include "common/system.h"
32 #include "common/file.h"
33 #include "common/fs.h"
34 #include "common/savefile.h"
35 #include "common/textconsole.h"
36 #include "common/translation.h"
37 
38 #include "engines/metaengine.h"
39 
40 static const PlainGameDescriptor skySetting =
41 	{"sky", "Beneath a Steel Sky" };
42 
43 static const ExtraGuiOption skyExtraGuiOption = {
44 	_s("Floppy intro"),
45 	_s("Use the floppy version's intro (CD version only)"),
46 	"alt_intro",
47 	false
48 };
49 
50 struct SkyVersion {
51 	int dinnerTableEntries;
52 	int dataDiskSize;
53 	const char *extraDesc;
54 	int version;
55 	const char *guioptions;
56 };
57 
58 // TODO: Would be nice if Disk::determineGameVersion() used this table, too.
59 static const SkyVersion skyVersions[] = {
60 	{  232, -1, "floppy demo", 272, GUIO1(GUIO_NOSPEECH) }, // German
61 	{  243, -1, "pc gamer demo", 109, GUIO1(GUIO_NOSPEECH) },
62 	{  247, -1, "floppy demo", 267, GUIO1(GUIO_NOSPEECH) }, // English
63 	{ 1404, -1, "floppy", 288, GUIO1(GUIO_NOSPEECH) },
64 	{ 1413, -1, "floppy", 303, GUIO1(GUIO_NOSPEECH) },
65 	{ 1445, 8830435, "floppy", 348, GUIO1(GUIO_NOSPEECH) },
66 	{ 1445, -1, "floppy", 331, GUIO1(GUIO_NOSPEECH) },
67 	{ 1711, -1, "cd demo", 365, GUIO0() },
68 	{ 5099, -1, "cd", 368, GUIO0() },
69 	{ 5097, -1, "cd", 372, GUIO0() },
70 	{ 0, 0, 0, 0, 0 }
71 };
72 
73 class SkyMetaEngine : public MetaEngine {
74 public:
75 	virtual const char *getName() const;
76 	virtual const char *getOriginalCopyright() const;
77 
78 	virtual bool hasFeature(MetaEngineFeature f) const;
79 	PlainGameList getSupportedGames() const override;
80 	virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const;
81 	PlainGameDescriptor findGame(const char *gameid) const override;
82 	DetectedGames detectGames(const Common::FSList &fslist) const override;
83 
84 	virtual Common::Error createInstance(OSystem *syst, Engine **engine) const;
85 
86 	virtual SaveStateList listSaves(const char *target) const;
87 	virtual int getMaximumSaveSlot() const;
88 	virtual void removeSaveState(const char *target, int slot) const;
89 };
90 
getName() const91 const char *SkyMetaEngine::getName() const {
92 	return "Beneath a Steel Sky";
93 }
94 
getOriginalCopyright() const95 const char *SkyMetaEngine::getOriginalCopyright() const {
96 	return "Beneath a Steel Sky (C) Revolution";
97 }
98 
hasFeature(MetaEngineFeature f) const99 bool SkyMetaEngine::hasFeature(MetaEngineFeature f) const {
100 	return
101 		(f == kSupportsListSaves) ||
102 		(f == kSupportsLoadingDuringStartup) ||
103 		(f == kSupportsDeleteSave);
104 }
105 
hasFeature(EngineFeature f) const106 bool Sky::SkyEngine::hasFeature(EngineFeature f) const {
107 	return
108 		(f == kSupportsRTL) ||
109 		(f == kSupportsLoadingDuringRuntime) ||
110 		(f == kSupportsSavingDuringRuntime);
111 }
112 
getSupportedGames() const113 PlainGameList SkyMetaEngine::getSupportedGames() const {
114 	PlainGameList games;
115 	games.push_back(skySetting);
116 	return games;
117 }
118 
getExtraGuiOptions(const Common::String & target) const119 const ExtraGuiOptions SkyMetaEngine::getExtraGuiOptions(const Common::String &target) const {
120 	Common::String guiOptions;
121 	ExtraGuiOptions options;
122 
123 	if (target.empty()) {
124 		options.push_back(skyExtraGuiOption);
125 		return options;
126 	}
127 
128 	if (ConfMan.hasKey("guioptions", target)) {
129 		guiOptions = ConfMan.get("guioptions", target);
130 		guiOptions = parseGameGUIOptions(guiOptions);
131 	}
132 
133 	if (!guiOptions.contains(GUIO_NOSPEECH))
134 		options.push_back(skyExtraGuiOption);
135 	return options;
136 }
137 
findGame(const char * gameid) const138 PlainGameDescriptor SkyMetaEngine::findGame(const char *gameid) const {
139 	if (0 == scumm_stricmp(gameid, skySetting.gameId))
140 		return skySetting;
141 	return PlainGameDescriptor::empty();
142 }
143 
detectGames(const Common::FSList & fslist) const144 DetectedGames SkyMetaEngine::detectGames(const Common::FSList &fslist) const {
145 	DetectedGames detectedGames;
146 	bool hasSkyDsk = false;
147 	bool hasSkyDnr = false;
148 	int dinnerTableEntries = -1;
149 	int dataDiskSize = -1;
150 
151 	// Iterate over all files in the given directory
152 	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
153 		if (!file->isDirectory()) {
154 			if (0 == scumm_stricmp("sky.dsk", file->getName().c_str())) {
155 				Common::File dataDisk;
156 				if (dataDisk.open(*file)) {
157 					hasSkyDsk = true;
158 					dataDiskSize = dataDisk.size();
159 				}
160 			}
161 
162 			if (0 == scumm_stricmp("sky.dnr", file->getName().c_str())) {
163 				Common::File dinner;
164 				if (dinner.open(*file)) {
165 					hasSkyDnr = true;
166 					dinnerTableEntries = dinner.readUint32LE();
167 				}
168 			}
169 		}
170 	}
171 
172 	if (hasSkyDsk && hasSkyDnr) {
173 		// Match found, add to list of candidates, then abort inner loop.
174 		// The game detector uses US English by default. We want British
175 		// English to match the recorded voices better.
176 		const SkyVersion *sv = skyVersions;
177 		while (sv->dinnerTableEntries) {
178 			if (dinnerTableEntries == sv->dinnerTableEntries &&
179 				(sv->dataDiskSize == dataDiskSize || sv->dataDiskSize == -1)) {
180 				break;
181 			}
182 			++sv;
183 		}
184 
185 		if (sv->dinnerTableEntries) {
186 			Common::String extra = Common::String::format("v0.0%d %s", sv->version, sv->extraDesc);
187 
188 			DetectedGame game = DetectedGame(skySetting.gameId, skySetting.description, Common::UNK_LANG, Common::kPlatformUnknown, extra);
189 			game.setGUIOptions(sv->guioptions);
190 
191 			detectedGames.push_back(game);
192 		} else {
193 			detectedGames.push_back(DetectedGame(skySetting.gameId, skySetting.description));
194 		}
195 	}
196 
197 	return detectedGames;
198 }
199 
createInstance(OSystem * syst,Engine ** engine) const200 Common::Error SkyMetaEngine::createInstance(OSystem *syst, Engine **engine) const {
201 	assert(engine);
202 	*engine = new Sky::SkyEngine(syst);
203 	return Common::kNoError;
204 }
205 
listSaves(const char * target) const206 SaveStateList SkyMetaEngine::listSaves(const char *target) const {
207 	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
208 	SaveStateList saveList;
209 
210 	// Load the descriptions
211 	Common::StringArray savenames;
212 	savenames.resize(MAX_SAVE_GAMES+1);
213 
214 	Common::InSaveFile *inf;
215 	inf = saveFileMan->openForLoading("SKY-VM.SAV");
216 	if (inf != NULL) {
217 		char *tmpBuf =  new char[MAX_SAVE_GAMES * MAX_TEXT_LEN];
218 		char *tmpPtr = tmpBuf;
219 		inf->read(tmpBuf, MAX_SAVE_GAMES * MAX_TEXT_LEN);
220 		for (int i = 0; i < MAX_SAVE_GAMES; ++i) {
221 			savenames[i] = tmpPtr;
222 			tmpPtr += savenames[i].size() + 1;
223 		}
224 		delete inf;
225 		delete[] tmpBuf;
226 	}
227 
228 	// Find all saves
229 	Common::StringArray filenames;
230 	filenames = saveFileMan->listSavefiles("SKY-VM.###");
231 
232 	// Slot 0 is the autosave, if it exists.
233 	// TODO: Check for the existence of the autosave -- but this require us
234 	// to know which SKY variant we are looking at.
235 	saveList.insert_at(0, SaveStateDescriptor(0, "*AUTOSAVE*"));
236 
237 	// Prepare the list of savestates by looping over all matching savefiles
238 	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
239 		// Extract the extension
240 		Common::String ext = file->c_str() + file->size() - 3;
241 		ext.toUppercase();
242 		int slotNum = atoi(ext.c_str());
243 		Common::InSaveFile *in = saveFileMan->openForLoading(*file);
244 		if (in) {
245 			saveList.push_back(SaveStateDescriptor(slotNum+1, savenames[slotNum]));
246 			delete in;
247 		}
248 	}
249 
250 	// Sort saves based on slot number.
251 	Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
252 	return saveList;
253 }
254 
getMaximumSaveSlot() const255 int SkyMetaEngine::getMaximumSaveSlot() const { return MAX_SAVE_GAMES; }
256 
removeSaveState(const char * target,int slot) const257 void SkyMetaEngine::removeSaveState(const char *target, int slot) const {
258 	if (slot == 0)	// do not delete the auto save
259 		return;
260 
261 	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
262 	char fName[20];
263 	sprintf(fName,"SKY-VM.%03d", slot - 1);
264 	saveFileMan->removeSavefile(fName);
265 
266 	// Load current save game descriptions
267 	Common::StringArray savenames;
268 	savenames.resize(MAX_SAVE_GAMES+1);
269 	Common::InSaveFile *inf;
270 	inf = saveFileMan->openForLoading("SKY-VM.SAV");
271 	if (inf != NULL) {
272 		char *tmpBuf =  new char[MAX_SAVE_GAMES * MAX_TEXT_LEN];
273 		char *tmpPtr = tmpBuf;
274 		inf->read(tmpBuf, MAX_SAVE_GAMES * MAX_TEXT_LEN);
275 		for (int i = 0; i < MAX_SAVE_GAMES; ++i) {
276 			savenames[i] = tmpPtr;
277 			tmpPtr += savenames[i].size() + 1;
278 		}
279 		delete inf;
280 		delete[] tmpBuf;
281 	}
282 
283 	// Update the save game description at the given slot
284 	savenames[slot - 1] = "";
285 
286 	// Save the updated descriptions
287 	Common::OutSaveFile *outf;
288 
289 	outf = saveFileMan->openForSaving("SKY-VM.SAV");
290 	bool ioFailed = true;
291 	if (outf) {
292 		for (uint16 cnt = 0; cnt < MAX_SAVE_GAMES; cnt++) {
293 			outf->write(savenames[cnt].c_str(), savenames[cnt].size() + 1);
294 		}
295 		outf->finalize();
296 		if (!outf->err())
297 			ioFailed = false;
298 		delete outf;
299 	}
300 	if (ioFailed)
301 		warning("Unable to store Savegame names to file SKY-VM.SAV. (%s)", saveFileMan->popErrorDesc().c_str());
302 }
303 
304 #if PLUGIN_ENABLED_DYNAMIC(SKY)
305 	REGISTER_PLUGIN_DYNAMIC(SKY, PLUGIN_TYPE_ENGINE, SkyMetaEngine);
306 #else
307 	REGISTER_PLUGIN_STATIC(SKY, PLUGIN_TYPE_ENGINE, SkyMetaEngine);
308 #endif
309 
310 namespace Sky {
loadGameState(int slot)311 Common::Error SkyEngine::loadGameState(int slot) {
312 	uint16 result = _skyControl->quickXRestore(slot);
313 	return (result == GAME_RESTORED) ? Common::kNoError : Common::kUnknownError;
314 }
315 
saveGameState(int slot,const Common::String & desc)316 Common::Error SkyEngine::saveGameState(int slot, const Common::String &desc) {
317 	if (slot == 0)
318 		return Common::kWritePermissionDenied;	// we can't overwrite the auto save
319 
320 	// Set the save slot and save the game
321 	_skyControl->_selectedGame = slot - 1;
322 	if (_skyControl->saveGameToFile(false) != GAME_SAVED)
323 		return Common::kWritePermissionDenied;
324 
325 	// Load current save game descriptions
326 	Common::StringArray saveGameTexts;
327 	saveGameTexts.resize(MAX_SAVE_GAMES+1);
328 	_skyControl->loadDescriptions(saveGameTexts);
329 
330 	// Update the save game description at the given slot
331 	saveGameTexts[slot - 1] = desc;
332 	// Save the updated descriptions
333 	_skyControl->saveDescriptions(saveGameTexts);
334 
335 	return Common::kNoError;
336 }
337 
canLoadGameStateCurrently()338 bool SkyEngine::canLoadGameStateCurrently() {
339 	return _systemVars.pastIntro && _skyControl->loadSaveAllowed();
340 }
341 
canSaveGameStateCurrently()342 bool SkyEngine::canSaveGameStateCurrently() {
343 	return _systemVars.pastIntro && _skyControl->loadSaveAllowed();
344 }
345 
346 } // End of namespace Sky
347