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 "gui/saveload.h"
24
25 #include "graphics/thumbnail.h"
26
27 #include "common/file.h"
28 #include "common/savefile.h"
29 #include "common/serializer.h"
30 #include "common/translation.h"
31
32 #include "startrek/resource.h"
33 #include "startrek/room.h"
34 #include "startrek/startrek.h"
35
36 namespace StarTrek {
37
showSaveMenu()38 bool StarTrekEngine::showSaveMenu() {
39 GUI::SaveLoadChooser *dialog;
40 Common::String desc;
41 int slot;
42
43 dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
44
45 slot = dialog->runModalWithCurrentTarget();
46 desc = dialog->getResultString();
47
48 if (desc.empty()) {
49 // create our own description for the saved game, the user didnt enter it
50 desc = dialog->createDefaultSaveDescription(slot);
51 }
52
53 if (desc.size() > 28)
54 desc = Common::String(desc.c_str(), 28);
55
56 delete dialog;
57
58 if (slot < 0)
59 return true;
60
61 return saveGame(slot, desc);
62 }
63
showLoadMenu()64 bool StarTrekEngine::showLoadMenu() {
65 GUI::SaveLoadChooser *dialog;
66 int slot;
67
68 dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
69 slot = dialog->runModalWithCurrentTarget();
70
71 delete dialog;
72
73 if (slot < 0)
74 return true;
75
76 return loadGame(slot);
77 }
78
79 const uint32 CURRENT_SAVEGAME_VERSION = 1;
80
saveGame(int slot,Common::String desc)81 bool StarTrekEngine::saveGame(int slot, Common::String desc) {
82 Common::String filename = getSavegameFilename(slot);
83 Common::OutSaveFile *out;
84
85 if (!(out = _saveFileMan->openForSaving(filename))) {
86 warning("Can't create file '%s', game not saved", filename.c_str());
87 return false;
88 } else {
89 debug(3, "Successfully opened %s for writing", filename.c_str());
90 }
91
92 SavegameMetadata meta;
93 meta.version = CURRENT_SAVEGAME_VERSION;
94 memset(meta.description, 0, sizeof(meta.description));
95 strncpy(meta.description, desc.c_str(), SAVEGAME_DESCRIPTION_LEN);
96
97 TimeDate curTime;
98 _system->getTimeAndDate(curTime);
99 meta.setSaveTimeAndDate(curTime);
100 meta.playTime = g_engine->getTotalPlayTime();
101
102 if (!saveOrLoadMetadata(nullptr, out, &meta)) {
103 delete out;
104 return false;
105 }
106 if (!saveOrLoadGameData(nullptr, out, &meta)) {
107 delete out;
108 return false;
109 }
110
111 out->finalize();
112 delete out;
113 return true;
114 }
115
loadGame(int slot)116 bool StarTrekEngine::loadGame(int slot) {
117 Common::String filename = getSavegameFilename(slot);
118 Common::InSaveFile *in;
119
120 if (!(in = _saveFileMan->openForLoading(filename))) {
121 warning("Can't open file '%s', game not loaded", filename.c_str());
122 return false;
123 } else {
124 debug(3, "Successfully opened %s for loading", filename.c_str());
125 }
126
127 SavegameMetadata meta;
128 if (!saveOrLoadMetadata(in, nullptr, &meta)) {
129 delete in;
130 return false;
131 }
132
133 if (meta.version > CURRENT_SAVEGAME_VERSION) {
134 delete in;
135 error("Savegame version (%u) is newer than current version (%u). A newer version of ScummVM is needed", meta.version, CURRENT_SAVEGAME_VERSION);
136 }
137
138 if (!saveOrLoadGameData(in, nullptr, &meta)) {
139 delete in;
140 return false;
141 }
142
143 delete in;
144
145 _lastGameMode = _gameMode;
146
147 if (_gameMode == GAMEMODE_AWAYMISSION) {
148 for (int i = 0; i < NUM_ACTORS; i++) {
149 Actor *a = &_actorList[i];
150 if (a->spriteDrawn) {
151 if (a->animType != 1)
152 a->animFile = SharedPtr<Common::MemoryReadStreamEndian>(_resource->loadFile(a->animFilename + ".anm"));
153 _gfx->addSprite(&a->sprite);
154 a->sprite.setBitmap(loadAnimationFrame(a->bitmapFilename, a->scale));
155 }
156 }
157 } else if (_gameMode == -1) {
158 initBridge(true);
159 _lastGameMode = GAMEMODE_BRIDGE;
160 // TODO: mode change
161 } else {
162 _resource->setTxtFileName(_missionToLoad);
163 initBridge(false);
164 // TODO: mode change
165 }
166
167 return true;
168 }
169
saveOrLoadGameData(Common::SeekableReadStream * in,Common::WriteStream * out,SavegameMetadata * meta)170 bool StarTrekEngine::saveOrLoadGameData(Common::SeekableReadStream *in, Common::WriteStream *out, SavegameMetadata *meta) {
171 Common::Serializer ser(in, out);
172
173 if (ser.isLoading()) {
174 if (_lastGameMode == GAMEMODE_BRIDGE)
175 cleanupBridge();
176 else // Assume GAMEMODE_AWAYMISSION
177 unloadRoom();
178 }
179
180 ser.syncAsUint16LE(_gameMode);
181 // TODO: sub_1d8eb (save) / sub_1d958 (load) (probably bridge / space combat state)
182
183 Common::String midiFilename = _sound->_loadedMidiFilename;
184 ser.syncString(midiFilename);
185 ser.syncAsSint16LE(_sound->_loopingMidiTrack);
186
187 if (ser.isLoading()) {
188 if (midiFilename.empty())
189 _sound->clearAllMidiSlots();
190 else {
191 _sound->loadMusicFile(midiFilename);
192 _sound->playMidiMusicTracks(_sound->_loopingMidiTrack, _sound->_loopingMidiTrack);
193 }
194 }
195
196 ser.syncAsUint16LE(_frameIndex);
197 ser.syncAsUint16LE(_mouseControllingShip);
198 if (meta->version >= 1) {
199 ser.syncAsSint16LE(_enterpriseState.inOrbit);
200 ser.syncAsSint16LE(_enterpriseState.underAttack);
201 ser.syncAsSint16LE(_randomEncounterType);
202
203 int16 unkFlag1 = 0; // TODO: word_5082e (either 0 or 1)
204 ser.syncAsSint16LE(unkFlag1);
205
206 int unkVar1 = 0; // TODO: dword_519b0
207 ser.syncAsSint32LE(unkVar1);
208
209 ser.syncAsSint16LE(_currentPlanet);
210 ser.syncAsSint16LE(_targetPlanet);
211
212 int16 unkFlag2 = 0; // TODO: word_45ab8 (either 0 or -1)
213 ser.syncAsSint16LE(unkFlag2);
214 }
215
216 ser.syncString(_missionToLoad);
217
218 if (meta->version >= 1) {
219 int16 unkFlag3 = 0; // TODO: word_4b032 (either 0 or 1)
220 ser.syncAsSint16LE(unkFlag3);
221
222 ser.syncAsUint16LE(_hailedTarget);
223 ser.syncAsSint16LE(_lastMissionId);
224 for (int i = 0; i < 7; i++) {
225 ser.syncAsUint16LE(_missionPoints[i]);
226 }
227 }
228
229 ser.syncString(_sound->_loopingAudioName);
230
231 if (ser.isLoading()) {
232 if (!_sound->_loopingAudioName.empty())
233 _sound->playVoc(_sound->_loopingAudioName);
234 }
235
236 if (meta->version >= 1) {
237 ser.syncAsSint16LE(_bridgeSequenceToLoad);
238 }
239
240 for (int i = 0; i < NUM_OBJECTS; i++) {
241 ser.syncAsByte(_itemList[i].have);
242 }
243
244 if (_gameMode == GAMEMODE_AWAYMISSION) {
245 ser.syncString(_missionName);
246 ser.syncAsSint16LE(_roomIndex);
247
248 if (ser.isLoading()) {
249 _gfx->fadeoutScreen();
250 _resource->setTxtFileName("ground");
251
252 // This must be done before loading the actor variables, since this clears
253 // them.
254 loadRoom(_missionName, _roomIndex);
255 }
256
257 ser.syncAsUint32LE(_roomFrameCounter);
258 ser.syncAsUint32LE(_frameIndex); // FIXME: redundant
259
260 byte filler = 0;
261
262 // Serialize the "actor" class
263 for (int i = 0; i < NUM_ACTORS; i++) {
264 Actor *a = &_actorList[i];
265 ser.syncAsUint16LE(a->spriteDrawn);
266 ser.syncString(a->animFilename);
267 if (a->animFilename.size() < 15) {
268 filler = 0;
269 for (uint j = 0; j < 16 - a->animFilename.size() - 1; ++j)
270 ser.syncAsByte(filler); // make sure that exactly 16 bytes are synced
271 }
272 a->animFilename.trim();
273
274 ser.syncAsUint16LE(a->animType);
275
276 a->sprite.saveLoadWithSerializer(ser);
277
278 ser.syncString(a->bitmapFilename);
279 if (a->bitmapFilename.size() < 9) {
280 filler = 0;
281 for (uint j = 0; j < 10 - a->bitmapFilename.size() - 1; ++j)
282 ser.syncAsByte(filler); // make sure that exactly 10 bytes are synced
283 }
284 a->bitmapFilename.trim();
285
286 a->scale.saveLoadWithSerializer(ser);
287 // Can't save "animFile" (will be reloaded)
288 ser.syncAsUint16LE(a->numAnimFrames);
289 ser.syncAsUint16LE(a->animFrame);
290 ser.syncAsUint32LE(a->frameToStartNextAnim);
291 ser.syncAsSint16LE(a->pos.x);
292 ser.syncAsSint16LE(a->pos.y);
293 ser.syncAsUint16LE(a->field60);
294 ser.syncAsUint16LE(a->field62);
295 ser.syncAsUint16LE(a->triggerActionWhenAnimFinished);
296 ser.syncAsUint16LE(a->finishedAnimActionParam);
297 ser.syncString(a->animationString2);
298 if (a->animationString2.size() < 7) {
299 filler = 0;
300 for (uint j = 0; j < 8 - a->animationString2.size() - 1; ++j)
301 ser.syncAsByte(filler); // make sure that exactly 8 bytes are synced
302 }
303 ser.syncAsUint16LE(a->field70);
304 ser.syncAsUint16LE(a->field72);
305 ser.syncAsUint16LE(a->field74);
306 ser.syncAsUint16LE(a->field76);
307 ser.syncAsSint16LE(a->iwSrcPosition);
308 ser.syncAsSint16LE(a->iwDestPosition);
309 a->granularPosX.saveLoadWithSerializer(ser);
310 a->granularPosY.saveLoadWithSerializer(ser);
311 a->speedX.saveLoadWithSerializer(ser);
312 a->speedY.saveLoadWithSerializer(ser);
313 ser.syncAsSint16LE(a->dest.x);
314 ser.syncAsSint16LE(a->dest.y);
315 ser.syncAsUint16LE(a->field90);
316 ser.syncAsByte(a->field92);
317 ser.syncAsByte(a->direction);
318 ser.syncAsUint16LE(a->field94);
319 ser.syncAsUint16LE(a->field96);
320 ser.syncString(a->animationString);
321 if (a->animationString.size() < 9) {
322 filler = 0;
323 for (uint j = 0; j < 10 - a->animationString.size() - 1; ++j)
324 ser.syncAsByte(filler); // make sure that exactly 10 bytes are synced
325 }
326 ser.syncAsUint16LE(a->fielda2);
327 ser.syncAsUint16LE(a->fielda4);
328 ser.syncAsUint16LE(a->fielda6);
329 }
330
331 Common::String unused = getScreenName();
332 ser.syncString(unused);
333
334 // Away mission struct
335 for (int i = 0; i < 8; i++)
336 ser.syncAsSint16LE(_awayMission.timers[i]);
337 ser.syncAsSint16LE(_awayMission.mouseX);
338 ser.syncAsSint16LE(_awayMission.mouseY);
339 for (int i = 0; i < 4; i++)
340 ser.syncAsSint16LE(_awayMission.crewGetupTimers[i]);
341 ser.syncAsByte(_awayMission.disableWalking);
342 ser.syncAsByte(_awayMission.disableInput);
343 ser.syncAsByte(_awayMission.redshirtDead);
344 ser.syncAsByte(_awayMission.activeAction);
345 ser.syncAsByte(_awayMission.activeObject);
346 ser.syncAsByte(_awayMission.passiveObject);
347 ser.syncAsByte(_awayMission.rdfStillDoDefaultAction);
348 ser.syncAsByte(_awayMission.crewDownBitset);
349 for (int i = 0; i < 4; i++)
350 ser.syncAsByte(_awayMission.crewDirectionsAfterWalk[i]);
351
352 if (_missionName == "DEMON") {
353 _awayMission.demon.saveLoadWithSerializer(ser);
354 _room->_roomVar.demon.saveLoadWithSerializer(ser);
355 } else if (_missionName == "TUG") {
356 _awayMission.tug.saveLoadWithSerializer(ser);
357 _room->_roomVar.tug.saveLoadWithSerializer(ser);
358 } else if (_missionName == "LOVE") {
359 _awayMission.love.saveLoadWithSerializer(ser);
360 _room->_roomVar.love.saveLoadWithSerializer(ser);
361 } else if (_missionName == "MUDD") {
362 _awayMission.mudd.saveLoadWithSerializer(ser);
363 _room->_roomVar.mudd.saveLoadWithSerializer(ser);
364 } else if (_missionName == "FEATHER") {
365 _awayMission.feather.saveLoadWithSerializer(ser);
366 _room->_roomVar.feather.saveLoadWithSerializer(ser);
367 } else if (_missionName == "TRIAL") {
368 _awayMission.trial.saveLoadWithSerializer(ser);
369 _room->_roomVar.trial.saveLoadWithSerializer(ser);
370 } else if (_missionName == "SINS") {
371 _awayMission.sins.saveLoadWithSerializer(ser);
372 _room->_roomVar.sins.saveLoadWithSerializer(ser);
373 } else if (_missionName == "VENG") {
374 _awayMission.veng.saveLoadWithSerializer(ser);
375 _room->_roomVar.veng.saveLoadWithSerializer(ser);
376 }
377
378 // The action queue
379 if (ser.isLoading()) {
380 _actionQueue = Common::Queue<Action>();
381 int16 n = 0;
382 ser.syncAsSint16LE(n);
383 for (int i = 0; i < n; i++) {
384 Action a;
385 a.saveLoadWithSerializer(ser);
386 _actionQueue.push(a);
387 }
388 } else { // Saving
389 int16 n = _actionQueue.size();
390 ser.syncAsSint16LE(n);
391 for (int i = 0; i < n; i++) {
392 Action a = _actionQueue.pop();
393 a.saveLoadWithSerializer(ser);
394 _actionQueue.push(a);
395 }
396 }
397
398 // Original game located changes in RDF files and saved them. Since RDF files
399 // aren't modified directly here, that's skipped.
400
401 ser.syncAsSint16LE(_objectHasWalkPosition);
402 ser.syncAsSint16LE(_objectWalkPosition.x);
403 ser.syncAsSint16LE(_objectWalkPosition.y);
404
405 for (int i = 0; i < MAX_BUFFERED_WALK_ACTIONS; i++) {
406 _actionOnWalkCompletion[i].saveLoadWithSerializer(ser);
407 ser.syncAsByte(_actionOnWalkCompletionInUse[i]);
408 }
409
410 ser.syncAsSint16LE(_warpHotspotsActive);
411 }
412
413 return true;
414 }
415
getSavegameFilename(int slotId) const416 Common::String StarTrekEngine::getSavegameFilename(int slotId) const {
417 Common::String saveLoadSlot = _targetName;
418 saveLoadSlot += Common::String::format(".%.3d", slotId);
419 return saveLoadSlot;
420 }
421
422
423 // Static function (reused in detection.cpp)
saveOrLoadMetadata(Common::SeekableReadStream * in,Common::WriteStream * out,SavegameMetadata * meta)424 bool saveOrLoadMetadata(Common::SeekableReadStream *in, Common::WriteStream *out, SavegameMetadata *meta) {
425 Common::Serializer ser(in, out);
426
427 ser.syncAsUint32LE(meta->version);
428 ser.syncBytes((byte *)meta->description, SAVEGAME_DESCRIPTION_LEN + 1);
429
430 // Thumbnail
431 if (ser.isLoading()) {
432 if (!::Graphics::loadThumbnail(*in, meta->thumbnail))
433 meta->thumbnail = nullptr;
434 } else
435 ::Graphics::saveThumbnail(*out);
436
437 // Creation date/time
438 ser.syncAsUint32LE(meta->saveDate);
439 debugC(5, kDebugSavegame, "Save date: %d", meta->saveDate);
440 ser.syncAsUint16LE(meta->saveTime);
441 debugC(5, kDebugSavegame, "Save time: %d", meta->saveTime);
442 ser.syncAsByte(meta->saveTimeSecs); // write seconds of save time as well
443 ser.syncAsUint32LE(meta->playTime);
444 debugC(5, kDebugSavegame, "Play time: %d", meta->playTime);
445
446 return true;
447 }
448
449 } // End of namespace StarTrek
450