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 #include "common/archive.h"
23 #include "common/config-manager.h"
24 #include "common/file.h"
25 #include "common/savefile.h"
26 #include "common/system.h"
27 
28 #include "cryomni3d/versailles/engine.h"
29 
30 namespace CryOmni3D {
31 namespace Versailles {
32 
loadGameState(int slot)33 Common::Error CryOmni3DEngine_Versailles::loadGameState(int slot) {
34 	_loadedSave = slot + 1;
35 	_abortCommand = kAbortLoadGame;
36 	return Common::kNoError;
37 }
38 
saveGameState(int slot,const Common::String & desc)39 Common::Error CryOmni3DEngine_Versailles::saveGameState(int slot, const Common::String &desc) {
40 	saveGame(_isVisiting, slot + 1, desc);
41 	return Common::kNoError;
42 }
43 
getSaveFileName(bool visit,uint saveNum) const44 Common::String CryOmni3DEngine_Versailles::getSaveFileName(bool visit, uint saveNum) const {
45 	return Common::String::format("%s%s.%04u", _targetName.c_str(), visit ? "_visit" : "", saveNum);
46 }
47 
canVisit() const48 bool CryOmni3DEngine_Versailles::canVisit() const {
49 	// Build a custom SearchSet
50 	const Common::FSNode gameDataDir(ConfMan.get("path"));
51 	Common::SearchSet visitsSearchSet;
52 	visitsSearchSet.addSubDirectoryMatching(gameDataDir, "savegame/visite", 1);
53 	return visitsSearchSet.hasFile("game0001.sav");
54 }
55 
getSavesList(bool visit,Common::StringArray & saveNames)56 void CryOmni3DEngine_Versailles::getSavesList(bool visit, Common::StringArray &saveNames) {
57 	char saveName[kSaveDescriptionLen + 1];
58 	saveName[kSaveDescriptionLen] = '\0';
59 	Common::String pattern = Common::String::format("%s%s.????", _targetName.c_str(),
60 	                         visit ? "_visit" : "");
61 	Common::StringArray filenames = _saveFileMan->listSavefiles(pattern);
62 	sort(filenames.begin(), filenames.end());   // Sort (hopefully ensuring we are sorted numerically..)
63 
64 	saveNames.clear();
65 	saveNames.reserve(100);
66 
67 	int num = 1;
68 	int slotNum;
69 
70 	if (visit) {
71 		// Add bootstrap visit
72 		const Common::FSNode gameDataDir(ConfMan.get("path"));
73 		Common::SearchSet visitsSearchSet;
74 		visitsSearchSet.addSubDirectoryMatching(gameDataDir, "savegame/visite", 1);
75 		if (visitsSearchSet.hasFile("game0001.sav")) {
76 			Common::File visitFile;
77 			if (!visitFile.open("game0001.sav", visitsSearchSet)) {
78 				error("Can't load visit file");
79 			}
80 			visitFile.read(saveName, kSaveDescriptionLen);
81 			saveNames.push_back(saveName);
82 		} else {
83 			warning("visiting mode but no bootstrap");
84 			// No bootstrap visit, too bad
85 			saveNames.push_back(_messages[55]); //Fill with free slot
86 		}
87 		num++;
88 	}
89 
90 	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end();
91 	        ++file) {
92 		// Obtain the last 4 digits of the filename, since they correspond to the save slot
93 		slotNum = atoi(file->c_str() + file->size() - 4);
94 
95 		if (slotNum >= 1 && slotNum <= 99) {
96 			while (num < slotNum) {
97 				saveNames.push_back(_messages[55]); //Fill with free slot
98 				num++;
99 			}
100 
101 			num++;
102 			Common::InSaveFile *in = _saveFileMan->openForLoading(*file);
103 			if (in) {
104 				if (in->read(saveName, kSaveDescriptionLen) == kSaveDescriptionLen) {
105 					saveNames.push_back(saveName);
106 				}
107 				delete in;
108 			}
109 		}
110 	}
111 
112 	for (uint i = saveNames.size(); i < 100; i++) {
113 		saveNames.push_back(_messages[55]);
114 	}
115 }
116 
saveGame(bool visit,uint saveNum,const Common::String & saveName)117 void CryOmni3DEngine_Versailles::saveGame(bool visit, uint saveNum,
118         const Common::String &saveName) {
119 	if (visit && saveNum == 1) {
120 		error("Can't erase bootstrap visit");
121 	}
122 
123 	Common::String saveFileName = getSaveFileName(visit, saveNum);
124 
125 	Common::OutSaveFile *out;
126 
127 	if (!(out = _saveFileMan->openForSaving(saveFileName))) {
128 		return;
129 	}
130 
131 	// Sync countdown to game variable before saving it to file
132 	syncCountdown();
133 
134 	// Write save name
135 	char saveNameC[kSaveDescriptionLen];
136 	memset(saveNameC, 0, sizeof(saveNameC));
137 	// Silence -Wstringop-truncation using parentheses, we don't have to have a null-terminated string here
138 	(strncpy(saveNameC, saveName.c_str(), sizeof(saveNameC)));
139 	out->write(saveNameC, sizeof(saveNameC));
140 
141 	// dummy values
142 	out->writeUint32LE(0);
143 	out->writeUint32BE(0);
144 	out->writeUint32BE(0);
145 
146 	// Dialog variables
147 	assert(_dialogsMan.size() < 200);
148 	for (uint i = 0; i < _dialogsMan.size(); i++) {
149 		out->writeByte(_dialogsMan[i]);
150 	}
151 	for (uint i = _dialogsMan.size(); i < 200; i++) {
152 		out->writeByte(0);
153 	}
154 
155 	// Inventory
156 	assert(_inventory.size() == 50);
157 	for (Inventory::const_iterator it = _inventory.begin(); it != _inventory.end(); it++) {
158 		uint objId = uint(-1);
159 		if (*it != nullptr) {
160 			// Inventory contains pointers to objects stored in _objects
161 			objId = *it - _objects.begin();
162 		}
163 		out->writeUint32BE(objId);
164 	}
165 	// Offset of inventory in toolbar
166 	out->writeUint32BE(_toolbar.inventoryOffset());
167 
168 	// Level, place, warp position
169 	out->writeUint32BE(_currentLevel);
170 	out->writeUint32BE(_currentPlaceId);
171 	out->writeDoubleBE(_omni3dMan.getAlpha());
172 	out->writeDoubleBE(_omni3dMan.getBeta());
173 
174 	// Places states
175 	assert(_placeStates.size() <= 100);
176 	Common::Array<PlaceState>::const_iterator placeIt = _placeStates.begin();
177 	for (uint i = 0; placeIt != _placeStates.end(); placeIt++, i++) {
178 		out->writeUint32BE(placeIt->state);
179 	}
180 	for (uint i = _placeStates.size(); i < 100; i++) {
181 		out->writeUint32BE(0);
182 	}
183 
184 	// Game variables
185 	assert(_gameVariables.size() < 100);
186 	for (Common::Array<uint>::const_iterator it = _gameVariables.begin();
187 	        it != _gameVariables.end(); it++) {
188 		out->writeUint32BE(*it);
189 	}
190 	for (uint i = _gameVariables.size(); i < 100; i++) {
191 		out->writeUint32BE(0);
192 	}
193 
194 	out->finalize();
195 
196 	delete out;
197 }
198 
loadGame(bool visit,uint saveNum)199 bool CryOmni3DEngine_Versailles::loadGame(bool visit, uint saveNum) {
200 	Common::SeekableReadStream *in;
201 
202 	if (visit && saveNum == 1) {
203 		// Load bootstrap visit
204 		const Common::FSNode gameDataDir(ConfMan.get("path"));
205 		Common::SearchSet visitsSearchSet;
206 		visitsSearchSet.addSubDirectoryMatching(gameDataDir, "savegame/visite", 1);
207 		Common::File *visitFile = new Common::File();
208 		if (!visitFile->open("game0001.sav", visitsSearchSet)) {
209 			delete visitFile;
210 			error("Can't load visit file");
211 		}
212 		in = visitFile;
213 	} else {
214 		Common::String saveFileName = getSaveFileName(visit, saveNum);
215 		in = _saveFileMan->openForLoading(saveFileName);
216 	}
217 
218 	if (!in || in->size() != 1260) {
219 		return false;
220 	}
221 
222 	musicStop();
223 
224 	// Load save name but don't use it
225 	char saveNameC[kSaveDescriptionLen];
226 	in->read(saveNameC, sizeof(saveNameC));
227 
228 	// dummy values
229 	(void) in->readUint32LE();
230 	(void) in->readUint32BE();
231 	(void) in->readUint32BE();
232 
233 	// Dialog variables
234 	assert(_dialogsMan.size() < 200);
235 	for (uint i = 0; i < _dialogsMan.size(); i++) {
236 		_dialogsMan[i] = in->readByte();
237 	}
238 	for (uint i = _dialogsMan.size(); i < 200; i++) {
239 		// Read the remaining bytes but don't use them
240 		(void) in->readByte();
241 	}
242 
243 	// Inventory
244 	assert(_inventory.size() == 50);
245 	for (Inventory::iterator it = _inventory.begin(); it != _inventory.end(); it++) {
246 		uint objId = in->readUint32BE();
247 		if (objId >= _objects.size()) {
248 			objId = uint(-1);
249 		}
250 		if (objId != uint(-1)) {
251 			*it = _objects.begin() + objId;
252 		} else {
253 			*it = nullptr;
254 		}
255 	}
256 	// Offset of inventory in toolbar
257 	_toolbar.setInventoryOffset(in->readUint32BE());
258 
259 	// Level, place, warp position
260 	_currentLevel = in->readUint32BE();
261 	// Use nextPlace to force place move
262 	_nextPlaceId = in->readUint32BE();
263 
264 	// Store alpha and beta for later use
265 	double alpha = in->readDoubleBE();
266 	double beta = in->readDoubleBE();
267 
268 	// Places states
269 	// Store them and use them once we called initNewLevel, we can't call it before because it needs _gameVariables (and especially kCurrentTime) to be correctly set
270 	uint32 placesStates[100];
271 	for (uint i = 0; i < 100; i++) {
272 		placesStates[i] = in->readUint32BE();
273 	}
274 
275 	// Game variables
276 	assert(_gameVariables.size() < 100);
277 	for (Common::Array<uint>::iterator it = _gameVariables.begin(); it != _gameVariables.end();
278 	        it++) {
279 		*it = in->readUint32BE();
280 	}
281 	for (uint i = _gameVariables.size(); i < 100; i++) {
282 		// Read the remaining variables but don't use them
283 		(void) in->readUint32BE();
284 	}
285 
286 	delete in;
287 
288 	if (_gameVariables[GameVariables::kCurrentTime] == 0) {
289 		_gameVariables[GameVariables::kCurrentTime] = 1;
290 	}
291 	initCountdown();
292 
293 	// Everything has been loaded, setup new level
294 	// We will set places states and warp coordinates just after that to avoid them from being reset
295 	initNewLevel(_currentLevel);
296 
297 	_omni3dMan.setAlpha(alpha);
298 	_omni3dMan.setBeta(beta);
299 
300 	// _placeStates has just been resized in initNewLevel
301 	uint i = 0;
302 	for (Common::Array<PlaceState>::iterator placeIt = _placeStates.begin();
303 	        placeIt != _placeStates.end() && i < ARRAYSIZE(placesStates); placeIt++, i++) {
304 		placeIt->state = placesStates[i];
305 	}
306 
307 	return true;
308 }
309 
310 } // End of namespace Versailles
311 } // End of namespace CryOmni3D
312