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