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 "common/system.h"
32 #include "common/translation.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 #include "saga/detection.h"
42 
43 namespace Saga {
44 
isBigEndian() const45 bool SagaEngine::isBigEndian() const { return isMacResources() && getGameId() == GID_ITE; }
isMacResources() const46 bool SagaEngine::isMacResources() const { return (getPlatform() == Common::kPlatformMacintosh); }
getResourceDescription() const47 const GameResourceDescription *SagaEngine::getResourceDescription() const { return _gameDescription->resourceDescription; }
48 
getFontDescription(int index) const49 const GameFontDescription *SagaEngine::getFontDescription(int index) const {
50 	assert(index < _gameDescription->fontsCount);
51 	return &_gameDescription->fontDescriptions[index];
52 }
getFontsCount() const53 int SagaEngine::getFontsCount() const { return _gameDescription->fontsCount; }
54 
getGameId() const55 int SagaEngine::getGameId() const { return _gameDescription->gameId; }
56 
getFeatures() const57 uint32 SagaEngine::getFeatures() const {
58 	uint32 result = _gameDescription->features;
59 
60 	return result;
61 }
62 
getLanguage() const63 Common::Language SagaEngine::getLanguage() const { return _gameDescription->desc.language; }
getPlatform() const64 Common::Platform SagaEngine::getPlatform() const { return _gameDescription->desc.platform; }
getGameNumber() const65 int SagaEngine::getGameNumber() const { return _gameNumber; }
getStartSceneNumber() const66 int SagaEngine::getStartSceneNumber() const { return _gameDescription->startSceneNumber; }
67 
getPatchDescriptions() const68 const GamePatchDescription *SagaEngine::getPatchDescriptions() const { return _gameDescription->patchDescriptions; }
getFilesDescriptions() const69 const ADGameFileDescription *SagaEngine::getFilesDescriptions() const { return _gameDescription->desc.filesDescriptions; }
70 
71 } // End of namespace Saga
72 
73 class SagaMetaEngine : public AdvancedMetaEngine {
74 public:
getName() const75 	const char *getName() const override {
76 		return "saga";
77 	}
78 
79 	bool hasFeature(MetaEngineFeature f) const override;
80 
81 	Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
82 
83 	SaveStateList listSaves(const char *target) const override;
84 	int getMaximumSaveSlot() const override;
85 	void removeSaveState(const char *target, int slot) const override;
86 	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
87 };
88 
hasFeature(MetaEngineFeature f) const89 bool SagaMetaEngine::hasFeature(MetaEngineFeature f) const {
90 	return
91 		(f == kSupportsListSaves) ||
92 		(f == kSupportsLoadingDuringStartup) ||
93 		(f == kSupportsDeleteSave) ||
94 		(f == kSavesSupportMetaInfo) ||
95 		(f == kSavesSupportThumbnail) ||
96 		(f == kSavesSupportCreationDate) ||
97 		(f == kSavesSupportPlayTime);
98 }
99 
hasFeature(EngineFeature f) const100 bool Saga::SagaEngine::hasFeature(EngineFeature f) const {
101 	return
102 		(f == kSupportsReturnToLauncher) ||
103 		(f == kSupportsLoadingDuringRuntime) ||
104 		(f == kSupportsSavingDuringRuntime);
105 }
106 
createInstance(OSystem * syst,Engine ** engine,const ADGameDescription * desc) const107 Common::Error SagaMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
108 	const Saga::SAGAGameDescription *gd = (const Saga::SAGAGameDescription *)desc;
109 
110 	switch (gd->gameId) {
111 	case Saga::GID_IHNM:
112 #ifndef ENABLE_IHNM
113 		return Common::Error(Common::kUnsupportedGameidError, _s("I Have No Mouth support not compiled in"));
114 #endif
115 		break;
116 	default:
117 		break;
118 	}
119 
120 	*engine = new Saga::SagaEngine(syst, gd);
121 	return Common::kNoError;
122 }
123 
listSaves(const char * target) const124 SaveStateList SagaMetaEngine::listSaves(const char *target) const {
125 	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
126 	Common::StringArray filenames;
127 	char saveDesc[SAVE_TITLE_SIZE];
128 	Common::String pattern = target;
129 	pattern += ".s##";
130 
131 	filenames = saveFileMan->listSavefiles(pattern);
132 
133 	SaveStateList saveList;
134 	int slotNum = 0;
135 	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
136 		// Obtain the last 2 digits of the filename, since they correspond to the save slot
137 		slotNum = atoi(file->c_str() + file->size() - 2);
138 
139 		if (slotNum >= 0 && slotNum < MAX_SAVES) {
140 			Common::InSaveFile *in = saveFileMan->openForLoading(*file);
141 			if (in) {
142 				for (int i = 0; i < 3; i++)
143 					in->readUint32BE();
144 				in->read(saveDesc, SAVE_TITLE_SIZE);
145 				saveList.push_back(SaveStateDescriptor(this, slotNum, saveDesc));
146 				delete in;
147 			}
148 		}
149 	}
150 
151 	// Sort saves based on slot number.
152 	Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
153 	return saveList;
154 }
155 
getMaximumSaveSlot() const156 int SagaMetaEngine::getMaximumSaveSlot() const { return MAX_SAVES - 1; }
157 
removeSaveState(const char * target,int slot) const158 void SagaMetaEngine::removeSaveState(const char *target, int slot) const {
159 	Common::String filename = target;
160 	filename += Common::String::format(".s%02d", slot);
161 
162 	g_system->getSavefileManager()->removeSavefile(filename);
163 }
164 
querySaveMetaInfos(const char * target,int slot) const165 SaveStateDescriptor SagaMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
166 	static char fileName[MAX_FILE_NAME];
167 	sprintf(fileName, "%s.s%02d", target, slot);
168 	char title[TITLESIZE];
169 
170 	Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName);
171 
172 	if (in) {
173 		uint32 type = in->readUint32BE();
174 		in->readUint32LE();		// size
175 		uint32 version = in->readUint32LE();
176 		char name[SAVE_TITLE_SIZE];
177 		in->read(name, sizeof(name));
178 
179 		SaveStateDescriptor desc(this, slot, name);
180 
181 		// Some older saves were not written in an endian safe fashion.
182 		// We try to detect this here by checking for extremely high version values.
183 		// If found, we retry with the data swapped.
184 		if (version > 0xFFFFFF) {
185 			warning("This savegame is not endian safe, retrying with the data swapped");
186 			version = SWAP_BYTES_32(version);
187 		}
188 
189 		debug(2, "Save version: 0x%X", version);
190 
191 		if (version < 4)
192 			warning("This savegame is not endian-safe. There may be problems");
193 
194 		if (type != MKTAG('S','A','G','A')) {
195 			error("SagaEngine::load wrong save game format");
196 		}
197 
198 		if (version > 4) {
199 			in->read(title, TITLESIZE);
200 			debug(0, "Save is for: %s", title);
201 		}
202 
203 		if (version >= 6) {
204 			Graphics::Surface *thumbnail;
205 			if (!Graphics::loadThumbnail(*in, thumbnail)) {
206 				delete in;
207 				return SaveStateDescriptor();
208 			}
209 			desc.setThumbnail(thumbnail);
210 
211 			uint32 saveDate = in->readUint32BE();
212 			uint16 saveTime = in->readUint16BE();
213 
214 			int day = (saveDate >> 24) & 0xFF;
215 			int month = (saveDate >> 16) & 0xFF;
216 			int year = saveDate & 0xFFFF;
217 
218 			desc.setSaveDate(year, month, day);
219 
220 			int hour = (saveTime >> 8) & 0xFF;
221 			int minutes = saveTime & 0xFF;
222 
223 			desc.setSaveTime(hour, minutes);
224 
225 			if (version >= 8) {
226 				uint32 playTime = in->readUint32BE();
227 				desc.setPlayTime(playTime * 1000);
228 			}
229 		}
230 
231 		delete in;
232 
233 		return desc;
234 	}
235 
236 	return SaveStateDescriptor();
237 }
238 
239 #if PLUGIN_ENABLED_DYNAMIC(SAGA)
240 	REGISTER_PLUGIN_DYNAMIC(SAGA, PLUGIN_TYPE_ENGINE, SagaMetaEngine);
241 #else
242 	REGISTER_PLUGIN_STATIC(SAGA, PLUGIN_TYPE_ENGINE, SagaMetaEngine);
243 #endif
244 
245 namespace Saga {
246 
initGame()247 bool SagaEngine::initGame() {
248 	_displayClip.right = getDisplayInfo().width;
249 	_displayClip.bottom = getDisplayInfo().height;
250 
251 	return _resource->createContexts();
252 }
253 
getDisplayInfo()254 const GameDisplayInfo &SagaEngine::getDisplayInfo() {
255 	switch (_gameDescription->gameId) {
256 		case GID_ITE:
257 			return ITE_DisplayInfo;
258 #ifdef ENABLE_IHNM
259 		case GID_IHNM:
260 			return IHNM_DisplayInfo;
261 #endif
262 		default:
263 			error("getDisplayInfo: Unknown game ID");
264 			return ITE_DisplayInfo;		// for compilers that don't support NORETURN
265 	}
266 }
267 
loadGameState(int slot)268 Common::Error SagaEngine::loadGameState(int slot) {
269 	// Init the current chapter to 8 (character selection) for IHNM
270 	if (getGameId() == GID_IHNM)
271 		_scene->changeScene(-2, 0, kTransitionFade, 8);
272 
273 	// First scene sets up palette
274 	_scene->changeScene(getStartSceneNumber(), 0, kTransitionNoFade);
275 	_events->handleEvents(0); // Process immediate events
276 
277 	if (getGameId() == GID_ITE)
278 		_interface->setMode(kPanelMain);
279 	else
280 		_interface->setMode(kPanelChapterSelection);
281 
282 	load(calcSaveFileName((uint)slot));
283 	syncSoundSettings();
284 
285 	return Common::kNoError;	// TODO: return success/failure
286 }
287 
saveGameState(int slot,const Common::String & desc,bool isAutosave)288 Common::Error SagaEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
289 	save(calcSaveFileName((uint)slot), desc.c_str());
290 	return Common::kNoError;	// TODO: return success/failure
291 }
292 
canLoadGameStateCurrently()293 bool SagaEngine::canLoadGameStateCurrently() {
294 	return !_scene->isInIntro() &&
295 		(_interface->getMode() == kPanelMain || _interface->getMode() == kPanelChapterSelection);
296 }
297 
canSaveGameStateCurrently()298 bool SagaEngine::canSaveGameStateCurrently() {
299 	return !_scene->isInIntro() &&
300 		(_interface->getMode() == kPanelMain || _interface->getMode() == kPanelChapterSelection);
301 }
302 
303 } // End of namespace Saga
304