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