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