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 "kyra/kyra_v1.h"
24 #include "kyra/engine/util.h"
25 
26 #include "common/savefile.h"
27 #include "common/system.h"
28 
29 #include "graphics/thumbnail.h"
30 #include "graphics/surface.h"
31 
32 #define CURRENT_SAVE_VERSION 18
33 
34 #define GF_FLOPPY  (1 <<  0)
35 #define GF_TALKIE  (1 <<  1)
36 #define GF_FMTOWNS (1 <<  2)
37 
38 namespace Kyra {
39 
readSaveHeader(Common::SeekableReadStream * in,SaveHeader & header,bool skipThumbnail)40 WARN_UNUSED_RESULT KyraEngine_v1::ReadSaveHeaderError KyraEngine_v1::readSaveHeader(Common::SeekableReadStream *in, SaveHeader &header, bool skipThumbnail) {
41 	uint32 type = in->readUint32BE();
42 	header.originalSave = false;
43 	header.oldHeader = false;
44 	header.flags = 0;
45 
46 	if (type == MKTAG('K', 'Y', 'R', 'A') || type == MKTAG('A', 'R', 'Y', 'K')) { // old Kyra1 header ID
47 		header.gameID = GI_KYRA1;
48 		header.oldHeader = true;
49 	} else if (type == MKTAG('H', 'O', 'F', 'S')) { // old Kyra2 header ID
50 		header.gameID = GI_KYRA2;
51 		header.oldHeader = true;
52 	} else if (type == MKTAG('W', 'W', 'S', 'V')) {
53 		header.gameID = in->readByte();
54 	} else {
55 		// try checking for original save header
56 		const int descriptionSize[3] = { 30, 80, 60 };
57 		char descriptionBuffer[81];
58 
59 		bool saveOk = false;
60 
61 		for (uint i = 0; i < ARRAYSIZE(descriptionSize) && !saveOk; ++i) {
62 			if (in->size() < descriptionSize[i] + 6)
63 				continue;
64 
65 			in->seek(0, SEEK_SET);
66 			in->read(descriptionBuffer, descriptionSize[i]);
67 			descriptionBuffer[descriptionSize[i]] = 0;
68 
69 			Util::convertDOSToISO(descriptionBuffer);
70 
71 			type = in->readUint32BE();
72 			header.version = in->readUint16LE();
73 			if (type == MKTAG('M', 'B', 'L', '3') && header.version == 100) {
74 				saveOk = true;
75 				header.description = descriptionBuffer;
76 				header.gameID = GI_KYRA2;
77 				break;
78 			} else if (type == MKTAG('M', 'B', 'L', '4') && header.version == 102) {
79 				saveOk = true;
80 				header.description = descriptionBuffer;
81 				header.gameID = GI_KYRA3;
82 				break;
83 			} else if (type == MKTAG('C','D','0','4')) {
84 				header.version = in->readUint32BE();
85 				// We don't check the minor version, since the original doesn't do that either and it isn't required.
86 				if (header.version != MKTAG(' ','C','D','1'))
87 					continue;
88 				saveOk = true;
89 				header.description = descriptionBuffer;
90 				header.gameID = GI_LOL;
91 				in->seek(6, SEEK_CUR);
92 				break;
93 			}
94 		}
95 
96 		if (saveOk) {
97 			header.originalSave = true;
98 			header.description = descriptionBuffer;
99 			return kRSHENoError;
100 		} else {
101 			return kRSHEInvalidType;
102 		}
103 	}
104 
105 	header.version = in->readUint32BE();
106 	if (header.version > CURRENT_SAVE_VERSION || (header.oldHeader && header.version > 8) || (type == MKTAG('A', 'R', 'Y', 'K') && header.version > 3))
107 		return kRSHEInvalidVersion;
108 
109 	// Versions prior to 9 are using a fixed length description field
110 	if (header.version <= 8) {
111 		char buffer[31];
112 		in->read(buffer, 31);
113 		// WORKAROUND: Old savegames could contain a missing termination 0 at the
114 		// end so we manually add it.
115 		buffer[30] = 0;
116 		header.description = buffer;
117 	} else {
118 		header.description = "";
119 		for (char c = 0; (c = in->readByte()) != 0;)
120 			header.description += c;
121 	}
122 
123 	if (header.version >= 2)
124 		header.flags = in->readUint32BE();
125 
126 	if (header.version >= 14) {
127 		if (!Graphics::loadThumbnail(*in, header.thumbnail, skipThumbnail)) {
128 			if (!skipThumbnail)
129 				return kRSHEIoError;
130 		}
131 	} else {
132 		header.thumbnail = 0;
133 	}
134 
135 	return ((in->err() || in->eos()) ? kRSHEIoError : kRSHENoError);
136 }
137 
openSaveForReading(const char * filename,SaveHeader & header,bool checkID)138 Common::SeekableReadStream *KyraEngine_v1::openSaveForReading(const char *filename, SaveHeader &header, bool checkID) {
139 	Common::SeekableReadStream *in = 0;
140 	if (!(in = _saveFileMan->openForLoading(filename)))
141 		return 0;
142 
143 	ReadSaveHeaderError errorCode = KyraEngine_v1::readSaveHeader(in, header);
144 	if (errorCode != kRSHENoError) {
145 		if (errorCode == kRSHEInvalidType)
146 			warning("No ScummVM Kyra engine savefile header");
147 		else if (errorCode == kRSHEInvalidVersion)
148 			warning("Savegame is not the right version (%u, '%s')", header.version, header.oldHeader ? "true" : "false");
149 		else if (errorCode == kRSHEIoError)
150 			warning("Load failed '%s'", filename);
151 
152 		delete in;
153 		return 0;
154 	}
155 
156 	if (!header.originalSave) {
157 		if (!header.oldHeader) {
158 			if (header.gameID != _flags.gameID && checkID) {
159 				warning("Trying to load saved game from other game (saved game: %u, running game: %u)", header.gameID, _flags.gameID);
160 				delete in;
161 				return 0;
162 			}
163 		}
164 
165 		if (header.version < 2) {
166 			warning("Make sure your savefile was from this version! (too old savefile version to detect that)");
167 		} else {
168 			if ((header.flags & GF_FLOPPY) && (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)) {
169 				warning("Can not load DOS Floppy savefile for this (non DOS Floppy) gameversion");
170 				delete in;
171 				return 0;
172 			} else if ((header.flags & GF_TALKIE) && !(_flags.isTalkie)) {
173 				warning("Can not load DOS CD-ROM savefile for this (non DOS CD-ROM) gameversion");
174 				delete in;
175 				return 0;
176 			} else if ((header.flags & GF_FMTOWNS) && !(_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)) {
177 				warning("Can not load FM-TOWNS/PC98 savefile for this (non FM-TOWNS/PC98) gameversion");
178 				delete in;
179 				return 0;
180 			}
181 		}
182 	}
183 
184 	return in;
185 }
186 
openSaveForWriting(const char * filename,const char * saveName,const Graphics::Surface * thumbnail) const187 Common::OutSaveFile *KyraEngine_v1::openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const {
188 	if (shouldQuit())
189 		return 0;
190 
191 	Common::WriteStream *out = 0;
192 	if (!(out = _saveFileMan->openForSaving(filename))) {
193 		warning("Can't create file '%s', game not saved", filename);
194 		return 0;
195 	}
196 
197 	// Savegame version
198 	out->writeUint32BE(MKTAG('W', 'W', 'S', 'V'));
199 	out->writeByte(_flags.gameID);
200 	out->writeUint32BE(CURRENT_SAVE_VERSION);
201 	out->write(saveName, strlen(saveName) + 1);
202 	if (_flags.isTalkie)
203 		out->writeUint32BE(GF_TALKIE);
204 	else if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)
205 		out->writeUint32BE(GF_FMTOWNS);
206 	else
207 		out->writeUint32BE(GF_FLOPPY);
208 
209 	if (out->err()) {
210 		warning("Can't write file '%s'. (Disk full?)", filename);
211 		delete out;
212 		return 0;
213 	}
214 
215 	Graphics::Surface *genThumbnail = 0;
216 	if (!thumbnail)
217 		thumbnail = genThumbnail = generateSaveThumbnail();
218 
219 	if (thumbnail)
220 		Graphics::saveThumbnail(*out, *thumbnail);
221 	else
222 		Graphics::saveThumbnail(*out);
223 
224 	if (genThumbnail) {
225 		genThumbnail->free();
226 		delete genThumbnail;
227 	}
228 
229 	return new Common::OutSaveFile(out);
230 }
231 
getSavegameFilename(int num)232 const char *KyraEngine_v1::getSavegameFilename(int num) {
233 	_savegameFilename = getSavegameFilename(_targetName, num);
234 	return _savegameFilename.c_str();
235 }
236 
getSavegameFilename(const Common::String & target,int num)237 Common::String KyraEngine_v1::getSavegameFilename(const Common::String &target, int num) {
238 	assert(num >= 0 && num <= 999);
239 	return target + Common::String::format(".%03d", num);
240 }
241 
saveFileLoadable(int slot)242 bool KyraEngine_v1::saveFileLoadable(int slot) {
243 	if (slot < 0 || slot > 999)
244 		return false;
245 
246 	SaveHeader header;
247 	Common::SeekableReadStream *in = openSaveForReading(getSavegameFilename(slot), header);
248 
249 	if (in) {
250 		delete in;
251 		return true;
252 	}
253 
254 	return false;
255 }
256 
checkAutosave()257 void KyraEngine_v1::checkAutosave() {
258 	if (shouldPerformAutoSave(_lastAutosave)) {
259 		saveGameStateIntern(999, "Autosave", 0);
260 		_lastAutosave = _system->getMillis();
261 	}
262 }
263 
loadGameStateCheck(int slot)264 void KyraEngine_v1::loadGameStateCheck(int slot) {
265 	// FIXME: Instead of throwing away the error returned by
266 	// loadGameState, we should use it / augment it.
267 	if (loadGameState(slot).getCode() != Common::kNoError) {
268 		const char *filename = getSavegameFilename(slot);
269 		Common::String errorMessage = "Could not load savegame: '";
270 		errorMessage += filename;
271 		errorMessage += "'";
272 
273 		GUIErrorMessage(errorMessage);
274 		error("%s", errorMessage.c_str());
275 	}
276 }
277 
278 } // End of namespace Kyra
279