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 // Game detection, general game parameters
24
25 #include "saga/saga.h"
26
27 #include "base/plugins.h"
28
29 #include "common/config-manager.h"
30 #include "engines/advancedDetector.h"
31 #include "engines/obsolete.h"
32 #include "common/system.h"
33 #include "graphics/thumbnail.h"
34
35 #include "saga/animation.h"
36 #include "saga/displayinfo.h"
37 #include "saga/events.h"
38 #include "saga/resource.h"
39 #include "saga/interface.h"
40 #include "saga/scene.h"
41
42 namespace Saga {
43 struct SAGAGameDescription {
44 ADGameDescription desc;
45
46 int gameId;
47 uint32 features;
48 int startSceneNumber;
49 const GameResourceDescription *resourceDescription;
50 int fontsCount;
51 const GameFontDescription *fontDescriptions;
52 const GamePatchDescription *patchDescriptions;
53 };
54
isBigEndian() const55 bool SagaEngine::isBigEndian() const { return isMacResources() && getGameId() == GID_ITE; }
isMacResources() const56 bool SagaEngine::isMacResources() const { return (getPlatform() == Common::kPlatformMacintosh); }
getResourceDescription() const57 const GameResourceDescription *SagaEngine::getResourceDescription() const { return _gameDescription->resourceDescription; }
58
getFontDescription(int index) const59 const GameFontDescription *SagaEngine::getFontDescription(int index) const {
60 assert(index < _gameDescription->fontsCount);
61 return &_gameDescription->fontDescriptions[index];
62 }
getFontsCount() const63 int SagaEngine::getFontsCount() const { return _gameDescription->fontsCount; }
64
getGameId() const65 int SagaEngine::getGameId() const { return _gameDescription->gameId; }
66
getFeatures() const67 uint32 SagaEngine::getFeatures() const {
68 uint32 result = _gameDescription->features;
69
70 return result;
71 }
72
getLanguage() const73 Common::Language SagaEngine::getLanguage() const { return _gameDescription->desc.language; }
getPlatform() const74 Common::Platform SagaEngine::getPlatform() const { return _gameDescription->desc.platform; }
getGameNumber() const75 int SagaEngine::getGameNumber() const { return _gameNumber; }
getStartSceneNumber() const76 int SagaEngine::getStartSceneNumber() const { return _gameDescription->startSceneNumber; }
77
getPatchDescriptions() const78 const GamePatchDescription *SagaEngine::getPatchDescriptions() const { return _gameDescription->patchDescriptions; }
getFilesDescriptions() const79 const ADGameFileDescription *SagaEngine::getFilesDescriptions() const { return _gameDescription->desc.filesDescriptions; }
80
81 }
82
83 static const PlainGameDescriptor sagaGames[] = {
84 {"saga", "SAGA Engine game"},
85 {"ite", "Inherit the Earth: Quest for the Orb"},
86 {"ihnm", "I Have No Mouth and I Must Scream"},
87 {"dino", "Dinotopia"},
88 {"fta2", "Faery Tale Adventure II: Halls of the Dead"},
89 {0, 0}
90 };
91
92 static const Engines::ObsoleteGameID obsoleteGameIDsTable[] = {
93 {"ite", "saga", Common::kPlatformUnknown},
94 {"ihnm", "saga", Common::kPlatformUnknown},
95 {"dino", "saga", Common::kPlatformUnknown},
96 {"fta2", "saga", Common::kPlatformUnknown},
97 {0, 0, Common::kPlatformUnknown}
98 };
99
100 #include "saga/detection_tables.h"
101
102 class SagaMetaEngine : public AdvancedMetaEngine {
103 public:
SagaMetaEngine()104 SagaMetaEngine() : AdvancedMetaEngine(Saga::gameDescriptions, sizeof(Saga::SAGAGameDescription), sagaGames) {
105 _singleId = "saga";
106 }
107
findGame(const char * gameId) const108 PlainGameDescriptor findGame(const char *gameId) const override {
109 return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable);
110 }
111
getName() const112 virtual const char *getName() const {
113 return "SAGA ["
114
115 #if defined(ENABLE_IHNM) && defined(ENABLE_SAGA2)
116 "all games"
117 #else
118 "ITE"
119
120 #if defined(ENABLE_IHNM)
121 ", IHNM"
122 #endif
123
124 #if defined(ENABLE_SAGA2)
125 ", SAGA2 games"
126 #endif
127
128 #endif
129 "]";
130
131 ;
132 }
133
getOriginalCopyright() const134 virtual const char *getOriginalCopyright() const {
135 return "Inherit the Earth (C) Wyrmkeep Entertainment";
136 }
137
138 virtual bool hasFeature(MetaEngineFeature f) const;
139
createInstance(OSystem * syst,Engine ** engine) const140 virtual Common::Error createInstance(OSystem *syst, Engine **engine) const {
141 Engines::upgradeTargetIfNecessary(obsoleteGameIDsTable);
142 return AdvancedMetaEngine::createInstance(syst, engine);
143 }
144 virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const;
145
146 virtual SaveStateList listSaves(const char *target) const;
147 virtual int getMaximumSaveSlot() const;
148 virtual void removeSaveState(const char *target, int slot) const;
149 SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
150 };
151
hasFeature(MetaEngineFeature f) const152 bool SagaMetaEngine::hasFeature(MetaEngineFeature f) const {
153 return
154 (f == kSupportsListSaves) ||
155 (f == kSupportsLoadingDuringStartup) ||
156 (f == kSupportsDeleteSave) ||
157 (f == kSavesSupportMetaInfo) ||
158 (f == kSavesSupportThumbnail) ||
159 (f == kSavesSupportCreationDate) ||
160 (f == kSavesSupportPlayTime) ||
161 (f == kSimpleSavesNames);
162 }
163
hasFeature(EngineFeature f) const164 bool Saga::SagaEngine::hasFeature(EngineFeature f) const {
165 return
166 (f == kSupportsRTL) ||
167 (f == kSupportsLoadingDuringRuntime) ||
168 (f == kSupportsSavingDuringRuntime);
169 }
170
createInstance(OSystem * syst,Engine ** engine,const ADGameDescription * desc) const171 bool SagaMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
172 const Saga::SAGAGameDescription *gd = (const Saga::SAGAGameDescription *)desc;
173 if (gd) {
174 *engine = new Saga::SagaEngine(syst, gd);
175 }
176 return gd != 0;
177 }
178
listSaves(const char * target) const179 SaveStateList SagaMetaEngine::listSaves(const char *target) const {
180 Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
181 Common::StringArray filenames;
182 char saveDesc[SAVE_TITLE_SIZE];
183 Common::String pattern = target;
184 pattern += ".s##";
185
186 filenames = saveFileMan->listSavefiles(pattern);
187
188 SaveStateList saveList;
189 int slotNum = 0;
190 for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
191 // Obtain the last 2 digits of the filename, since they correspond to the save slot
192 slotNum = atoi(file->c_str() + file->size() - 2);
193
194 if (slotNum >= 0 && slotNum < MAX_SAVES) {
195 Common::InSaveFile *in = saveFileMan->openForLoading(*file);
196 if (in) {
197 for (int i = 0; i < 3; i++)
198 in->readUint32BE();
199 in->read(saveDesc, SAVE_TITLE_SIZE);
200 saveList.push_back(SaveStateDescriptor(slotNum, saveDesc));
201 delete in;
202 }
203 }
204 }
205
206 // Sort saves based on slot number.
207 Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
208 return saveList;
209 }
210
getMaximumSaveSlot() const211 int SagaMetaEngine::getMaximumSaveSlot() const { return MAX_SAVES - 1; }
212
removeSaveState(const char * target,int slot) const213 void SagaMetaEngine::removeSaveState(const char *target, int slot) const {
214 Common::String filename = target;
215 filename += Common::String::format(".s%02d", slot);
216
217 g_system->getSavefileManager()->removeSavefile(filename);
218 }
219
querySaveMetaInfos(const char * target,int slot) const220 SaveStateDescriptor SagaMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
221 static char fileName[MAX_FILE_NAME];
222 sprintf(fileName, "%s.s%02d", target, slot);
223 char title[TITLESIZE];
224
225 Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName);
226
227 if (in) {
228 uint32 type = in->readUint32BE();
229 in->readUint32LE(); // size
230 uint32 version = in->readUint32LE();
231 char name[SAVE_TITLE_SIZE];
232 in->read(name, sizeof(name));
233
234 SaveStateDescriptor desc(slot, name);
235
236 // Some older saves were not written in an endian safe fashion.
237 // We try to detect this here by checking for extremely high version values.
238 // If found, we retry with the data swapped.
239 if (version > 0xFFFFFF) {
240 warning("This savegame is not endian safe, retrying with the data swapped");
241 version = SWAP_BYTES_32(version);
242 }
243
244 debug(2, "Save version: 0x%X", version);
245
246 if (version < 4)
247 warning("This savegame is not endian-safe. There may be problems");
248
249 if (type != MKTAG('S','A','G','A')) {
250 error("SagaEngine::load wrong save game format");
251 }
252
253 if (version > 4) {
254 in->read(title, TITLESIZE);
255 debug(0, "Save is for: %s", title);
256 }
257
258 if (version >= 6) {
259 Graphics::Surface *thumbnail;
260 if (!Graphics::loadThumbnail(*in, thumbnail)) {
261 delete in;
262 return SaveStateDescriptor();
263 }
264 desc.setThumbnail(thumbnail);
265
266 uint32 saveDate = in->readUint32BE();
267 uint16 saveTime = in->readUint16BE();
268
269 int day = (saveDate >> 24) & 0xFF;
270 int month = (saveDate >> 16) & 0xFF;
271 int year = saveDate & 0xFFFF;
272
273 desc.setSaveDate(year, month, day);
274
275 int hour = (saveTime >> 8) & 0xFF;
276 int minutes = saveTime & 0xFF;
277
278 desc.setSaveTime(hour, minutes);
279
280 if (version >= 8) {
281 uint32 playTime = in->readUint32BE();
282 desc.setPlayTime(playTime * 1000);
283 }
284 }
285
286 delete in;
287
288 return desc;
289 }
290
291 return SaveStateDescriptor();
292 }
293
294 #if PLUGIN_ENABLED_DYNAMIC(SAGA)
295 REGISTER_PLUGIN_DYNAMIC(SAGA, PLUGIN_TYPE_ENGINE, SagaMetaEngine);
296 #else
297 REGISTER_PLUGIN_STATIC(SAGA, PLUGIN_TYPE_ENGINE, SagaMetaEngine);
298 #endif
299
300 namespace Saga {
301
initGame()302 bool SagaEngine::initGame() {
303 _displayClip.right = getDisplayInfo().width;
304 _displayClip.bottom = getDisplayInfo().height;
305
306 return _resource->createContexts();
307 }
308
getDisplayInfo()309 const GameDisplayInfo &SagaEngine::getDisplayInfo() {
310 switch (_gameDescription->gameId) {
311 case GID_ITE:
312 return ITE_DisplayInfo;
313 #ifdef ENABLE_IHNM
314 case GID_IHNM:
315 return IHNM_DisplayInfo;
316 #endif
317 #ifdef ENABLE_SAGA2
318 case GID_DINO:
319 return FTA2_DisplayInfo; // TODO
320 case GID_FTA2:
321 return FTA2_DisplayInfo;
322 #endif
323 default:
324 error("getDisplayInfo: Unknown game ID");
325 return ITE_DisplayInfo; // for compilers that don't support NORETURN
326 }
327 }
328
loadGameState(int slot)329 Common::Error SagaEngine::loadGameState(int slot) {
330 // Init the current chapter to 8 (character selection) for IHNM
331 if (getGameId() == GID_IHNM)
332 _scene->changeScene(-2, 0, kTransitionFade, 8);
333
334 // First scene sets up palette
335 _scene->changeScene(getStartSceneNumber(), 0, kTransitionNoFade);
336 _events->handleEvents(0); // Process immediate events
337
338 if (getGameId() == GID_ITE)
339 _interface->setMode(kPanelMain);
340 else
341 _interface->setMode(kPanelChapterSelection);
342
343 load(calcSaveFileName((uint)slot));
344 syncSoundSettings();
345
346 return Common::kNoError; // TODO: return success/failure
347 }
348
saveGameState(int slot,const Common::String & desc)349 Common::Error SagaEngine::saveGameState(int slot, const Common::String &desc) {
350 save(calcSaveFileName((uint)slot), desc.c_str());
351 return Common::kNoError; // TODO: return success/failure
352 }
353
canLoadGameStateCurrently()354 bool SagaEngine::canLoadGameStateCurrently() {
355 return !_scene->isInIntro() &&
356 (_interface->getMode() == kPanelMain || _interface->getMode() == kPanelChapterSelection);
357 }
358
canSaveGameStateCurrently()359 bool SagaEngine::canSaveGameStateCurrently() {
360 return !_scene->isInIntro() &&
361 (_interface->getMode() == kPanelMain || _interface->getMode() == kPanelChapterSelection);
362 }
363
364 } // End of namespace Saga
365