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 "ags/engine/ac/character.h"
24 #include "ags/shared/ac/common.h"
25 #include "ags/engine/ac/draw.h"
26 #include "ags/engine/ac/dynamic_sprite.h"
27 #include "ags/engine/ac/event.h"
28 #include "ags/engine/ac/game.h"
29 #include "ags/shared/ac/game_setup_struct.h"
30 #include "ags/engine/ac/game_state.h"
31 #include "ags/engine/ac/game_setup.h"
32 #include "ags/engine/ac/global_audio.h"
33 #include "ags/engine/ac/global_character.h"
34 #include "ags/engine/ac/gui.h"
35 #include "ags/engine/ac/mouse.h"
36 #include "ags/engine/ac/overlay.h"
37 #include "ags/engine/ac/region.h"
38 #include "ags/engine/ac/rich_game_media.h"
39 #include "ags/engine/ac/room.h"
40 #include "ags/engine/ac/room_status.h"
41 #include "ags/shared/ac/sprite_cache.h"
42 #include "ags/engine/ac/system.h"
43 #include "ags/engine/ac/timer.h"
44 #include "ags/engine/debugging/debugger.h"
45 #include "ags/shared/debugging/out.h"
46 #include "ags/engine/device/mouse_w32.h"
47 #include "ags/shared/gfx/bitmap.h"
48 #include "ags/engine/gfx/ddb.h"
49 #include "ags/engine/gfx/graphics_driver.h"
50 #include "ags/engine/game/savegame.h"
51 #include "ags/engine/game/savegame_components.h"
52 #include "ags/engine/game/savegame_internal.h"
53 #include "ags/engine/main/engine.h"
54 #include "ags/engine/main/main.h"
55 #include "ags/engine/platform/base/ags_platform_driver.h"
56 #include "ags/engine/platform/base/sys_main.h"
57 #include "ags/plugins/ags_plugin.h"
58 #include "ags/plugins/plugin_engine.h"
59 #include "ags/engine/script/script.h"
60 #include "ags/shared/script/cc_error.h"
61 #include "ags/shared/util/aligned_stream.h"
62 #include "ags/shared/util/file.h"
63 #include "ags/shared/util/stream.h"
64 #include "ags/shared/util/string_utils.h"
65 #include "ags/shared/util/math.h"
66 #include "ags/engine/media/audio/audio_system.h"
67 #include "ags/globals.h"
68
69 namespace AGS3 {
70
71 using namespace Shared;
72 using namespace Engine;
73
74 // function is currently implemented in savegame_v321.cpp
75 HSaveError restore_game_data(Stream *in, SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data);
76
77 namespace AGS {
78 namespace Engine {
79
80 const char *SavegameSource::LegacySignature = "Adventure Game Studio saved game";
81 const char *SavegameSource::Signature = "Adventure Game Studio saved game v2";
82
SavegameSource()83 SavegameSource::SavegameSource()
84 : Version(kSvgVersion_Undefined) {
85 }
86
SavegameDescription()87 SavegameDescription::SavegameDescription()
88 : MainDataVersion(kGameVersion_Undefined)
89 , ColorDepth(0)
90 , LegacyID(0) {
91 }
92
PreservedParams()93 PreservedParams::PreservedParams()
94 : SpeechVOX(0)
95 , MusicVOX(0)
96 , GlScDataSize(0) {
97 }
98
ScriptData()99 RestoredData::ScriptData::ScriptData()
100 : Len(0) {
101 }
102
RestoredData()103 RestoredData::RestoredData()
104 : FPS(0)
105 , RoomVolume(kRoomVolumeNormal)
106 , CursorID(0)
107 , CursorMode(0) {
108 memset(RoomLightLevels, 0, sizeof(RoomLightLevels));
109 memset(RoomTintLevels, 0, sizeof(RoomTintLevels));
110 memset(RoomZoomLevels1, 0, sizeof(RoomZoomLevels1));
111 memset(RoomZoomLevels2, 0, sizeof(RoomZoomLevels2));
112 memset(DoAmbient, 0, sizeof(DoAmbient));
113 }
114
GetSavegameErrorText(SavegameErrorType err)115 String GetSavegameErrorText(SavegameErrorType err) {
116 switch (err) {
117 case kSvgErr_NoError:
118 return "No error.";
119 case kSvgErr_FileOpenFailed:
120 return "File not found or could not be opened.";
121 case kSvgErr_SignatureFailed:
122 return "Not an AGS saved game or unsupported format.";
123 case kSvgErr_FormatVersionNotSupported:
124 return "Save format version not supported.";
125 case kSvgErr_IncompatibleEngine:
126 return "Save was written by incompatible engine, or file is corrupted.";
127 case kSvgErr_GameGuidMismatch:
128 return "Game GUID does not match, saved by a different _GP(game).";
129 case kSvgErr_ComponentListOpeningTagFormat:
130 return "Failed to parse opening tag of the components list.";
131 case kSvgErr_ComponentListClosingTagMissing:
132 return "Closing tag of the components list was not met.";
133 case kSvgErr_ComponentOpeningTagFormat:
134 return "Failed to parse opening component tag.";
135 case kSvgErr_ComponentClosingTagFormat:
136 return "Failed to parse closing component tag.";
137 case kSvgErr_ComponentSizeMismatch:
138 return "Component data size mismatch.";
139 case kSvgErr_UnsupportedComponent:
140 return "Unknown and/or unsupported component.";
141 case kSvgErr_ComponentSerialization:
142 return "Failed to write the savegame component.";
143 case kSvgErr_ComponentUnserialization:
144 return "Failed to restore the savegame component.";
145 case kSvgErr_InconsistentFormat:
146 return "Inconsistent format, or file is corrupted.";
147 case kSvgErr_UnsupportedComponentVersion:
148 return "Component data version not supported.";
149 case kSvgErr_GameContentAssertion:
150 return "Saved content does not match current game.";
151 case kSvgErr_InconsistentData:
152 return "Inconsistent save data, or file is corrupted.";
153 case kSvgErr_InconsistentPlugin:
154 return "One of the game plugins did not restore its game data correctly.";
155 case kSvgErr_DifferentColorDepth:
156 return "Saved with the engine running at a different colour depth.";
157 case kSvgErr_GameObjectInitFailed:
158 return "Game object initialization failed after save restoration.";
159 default:
160 break;
161 }
162 return "Unknown error.";
163 }
164
RestoreSaveImage(Stream * in)165 Bitmap *RestoreSaveImage(Stream *in) {
166 if (in->ReadInt32())
167 return read_serialized_bitmap(in);
168 return nullptr;
169 }
170
SkipSaveImage(Stream * in)171 void SkipSaveImage(Stream *in) {
172 if (in->ReadInt32())
173 skip_serialized_bitmap(in);
174 }
175
ReadDescription(Stream * in,SavegameVersion & svg_ver,SavegameDescription & desc,SavegameDescElem elems)176 HSaveError ReadDescription(Stream *in, SavegameVersion &svg_ver, SavegameDescription &desc, SavegameDescElem elems) {
177 svg_ver = (SavegameVersion)in->ReadInt32();
178 if (svg_ver < kSvgVersion_LowestSupported || svg_ver > kSvgVersion_Current)
179 return new SavegameError(kSvgErr_FormatVersionNotSupported,
180 String::FromFormat("Required: %d, supported: %d - %d.", svg_ver, kSvgVersion_LowestSupported, kSvgVersion_Current));
181
182 // Enviroment information
183 if (svg_ver >= kSvgVersion_351)
184 in->ReadInt32(); // enviroment info size
185 if (elems & kSvgDesc_EnvInfo) {
186 desc.EngineName = StrUtil::ReadString(in);
187 desc.EngineVersion.SetFromString(StrUtil::ReadString(in));
188 desc.GameGuid = StrUtil::ReadString(in);
189 desc.GameTitle = StrUtil::ReadString(in);
190 desc.MainDataFilename = StrUtil::ReadString(in);
191 if (svg_ver >= kSvgVersion_Cmp_64bit)
192 desc.MainDataVersion = (GameDataVersion)in->ReadInt32();
193 desc.ColorDepth = in->ReadInt32();
194 if (svg_ver >= kSvgVersion_351)
195 desc.LegacyID = in->ReadInt32();
196 } else {
197 StrUtil::SkipString(in); // engine name
198 StrUtil::SkipString(in); // engine version
199 StrUtil::SkipString(in); // game guid
200 StrUtil::SkipString(in); // game title
201 StrUtil::SkipString(in); // main data filename
202 if (svg_ver >= kSvgVersion_Cmp_64bit)
203 in->ReadInt32(); // game data version
204 in->ReadInt32(); // color depth
205 if (svg_ver >= kSvgVersion_351)
206 in->ReadInt32(); // game legacy id
207 }
208 // User description
209 if (elems & kSvgDesc_UserText)
210 desc.UserText = StrUtil::ReadString(in);
211 else
212 StrUtil::SkipString(in);
213 if (elems & kSvgDesc_UserImage)
214 desc.UserImage.reset(RestoreSaveImage(in));
215 else
216 SkipSaveImage(in);
217
218 return HSaveError::None();
219 }
220
ReadDescription_v321(Stream * in,SavegameVersion & svg_ver,SavegameDescription & desc,SavegameDescElem elems)221 HSaveError ReadDescription_v321(Stream *in, SavegameVersion &svg_ver, SavegameDescription &desc, SavegameDescElem elems) {
222 // Legacy savegame header
223 if (elems & kSvgDesc_UserText)
224 desc.UserText.Read(in);
225 else
226 StrUtil::SkipCStr(in);
227 svg_ver = (SavegameVersion)in->ReadInt32();
228
229 // Check saved game format version
230 if (svg_ver < kSvgVersion_LowestSupported ||
231 svg_ver > kSvgVersion_Current) {
232 return new SavegameError(kSvgErr_FormatVersionNotSupported,
233 String::FromFormat("Required: %d, supported: %d - %d.", svg_ver, kSvgVersion_LowestSupported, kSvgVersion_Current));
234 }
235
236 if (elems & kSvgDesc_UserImage)
237 desc.UserImage.reset(RestoreSaveImage(in));
238 else
239 SkipSaveImage(in);
240
241 String version_str = String::FromStream(in);
242 Version eng_version(version_str);
243 if (eng_version > _G(EngineVersion) ||
244 eng_version < _G(SavedgameLowestBackwardCompatVersion)) {
245 // Engine version is either non-forward or non-backward compatible
246 return new SavegameError(kSvgErr_IncompatibleEngine,
247 String::FromFormat("Required: %s, supported: %s - %s.", eng_version.LongString.GetCStr(), _G(SavedgameLowestBackwardCompatVersion).LongString.GetCStr(), _G(EngineVersion).LongString.GetCStr()));
248 }
249 if (elems & kSvgDesc_EnvInfo) {
250 desc.MainDataFilename.Read(in);
251 in->ReadInt32(); // unscaled game height with borders, now obsolete
252 desc.ColorDepth = in->ReadInt32();
253 } else {
254 StrUtil::SkipCStr(in);
255 in->ReadInt32(); // unscaled game height with borders, now obsolete
256 in->ReadInt32(); // color depth
257 }
258
259 return HSaveError::None();
260 }
261
OpenSavegameBase(const String & filename,SavegameSource * src,SavegameDescription * desc,SavegameDescElem elems)262 HSaveError OpenSavegameBase(const String &filename, SavegameSource *src, SavegameDescription *desc, SavegameDescElem elems) {
263 UStream in(File::OpenFileRead(filename));
264 if (!in.get())
265 return new SavegameError(kSvgErr_FileOpenFailed, String::FromFormat("Requested filename: %s.", filename.GetCStr()));
266
267 // Skip MS Windows Vista rich media header
268 RICH_GAME_MEDIA_HEADER rich_media_header;
269 rich_media_header.ReadFromFile(in.get());
270
271 // Check saved game signature
272 bool is_new_save = false;
273 size_t pre_sig_pos = in->GetPosition();
274 String svg_sig = String::FromStreamCount(in.get(), strlen(SavegameSource::Signature));
275 if (svg_sig.Compare(SavegameSource::Signature) == 0) {
276 is_new_save = true;
277 } else {
278 in->Seek(pre_sig_pos, kSeekBegin);
279 svg_sig = String::FromStreamCount(in.get(), strlen(SavegameSource::LegacySignature));
280 if (svg_sig.Compare(SavegameSource::LegacySignature) != 0)
281 return new SavegameError(kSvgErr_SignatureFailed);
282 }
283
284 SavegameVersion svg_ver;
285 SavegameDescription temp_desc;
286 HSaveError err;
287 if (is_new_save)
288 err = ReadDescription(in.get(), svg_ver, temp_desc, desc ? elems : kSvgDesc_None);
289 else
290 err = ReadDescription_v321(in.get(), svg_ver, temp_desc, desc ? elems : kSvgDesc_None);
291 if (!err)
292 return err;
293
294 if (src) {
295 src->Filename = filename;
296 src->Version = svg_ver;
297 src->InputStream.reset(in.release()); // give the stream away to the caller
298 }
299 if (desc) {
300 if (elems & kSvgDesc_EnvInfo) {
301 desc->EngineName = temp_desc.EngineName;
302 desc->EngineVersion = temp_desc.EngineVersion;
303 desc->GameGuid = temp_desc.GameGuid;
304 desc->LegacyID = temp_desc.LegacyID;
305 desc->GameTitle = temp_desc.GameTitle;
306 desc->MainDataFilename = temp_desc.MainDataFilename;
307 desc->MainDataVersion = temp_desc.MainDataVersion;
308 desc->ColorDepth = temp_desc.ColorDepth;
309 }
310 if (elems & kSvgDesc_UserText)
311 desc->UserText = temp_desc.UserText;
312 if (elems & kSvgDesc_UserImage)
313 desc->UserImage.reset(temp_desc.UserImage.release());
314 }
315 return err;
316 }
317
OpenSavegame(const String & filename,SavegameSource & src,SavegameDescription & desc,SavegameDescElem elems)318 HSaveError OpenSavegame(const String &filename, SavegameSource &src, SavegameDescription &desc, SavegameDescElem elems) {
319 return OpenSavegameBase(filename, &src, &desc, elems);
320 }
321
OpenSavegame(const String & filename,SavegameDescription & desc,SavegameDescElem elems)322 HSaveError OpenSavegame(const String &filename, SavegameDescription &desc, SavegameDescElem elems) {
323 return OpenSavegameBase(filename, nullptr, &desc, elems);
324 }
325
326 // Prepares engine for actual save restore (stops processes, cleans up memory)
DoBeforeRestore(PreservedParams & pp)327 void DoBeforeRestore(PreservedParams &pp) {
328 pp.SpeechVOX = _GP(play).want_speech;
329 pp.MusicVOX = _GP(play).separate_music_lib;
330
331 unload_old_room();
332 delete _G(raw_saved_screen);
333 _G(raw_saved_screen) = nullptr;
334 remove_screen_overlay(-1);
335 _GP(play).complete_overlay_on = 0;
336 _GP(play).text_overlay_on = 0;
337
338 // cleanup dynamic sprites
339 // NOTE: sprite 0 is a special constant sprite that cannot be dynamic
340 for (int i = 1; i < (int)_GP(spriteset).GetSpriteSlotCount(); ++i) {
341 if (_GP(game).SpriteInfos[i].Flags & SPF_DYNAMICALLOC) {
342 // do this early, so that it changing _GP(guibuts) doesn't
343 // affect the restored data
344 free_dynamic_sprite(i);
345 }
346 }
347
348 // cleanup GUI backgrounds
349 for (int i = 0; i < _GP(game).numgui; ++i) {
350 delete _G(guibg)[i];
351 _G(guibg)[i] = nullptr;
352
353 if (_G(guibgbmp)[i])
354 _G(gfxDriver)->DestroyDDB(_G(guibgbmp)[i]);
355 _G(guibgbmp)[i] = nullptr;
356 }
357
358 // preserve script data sizes and cleanup scripts
359 pp.GlScDataSize = _G(gameinst)->globaldatasize;
360 delete _G(gameinstFork);
361 delete _G(gameinst);
362 _G(gameinstFork) = nullptr;
363 _G(gameinst) = nullptr;
364 pp.ScMdDataSize.resize(_G(numScriptModules));
365 for (int i = 0; i < _G(numScriptModules); ++i) {
366 pp.ScMdDataSize[i] = _GP(moduleInst)[i]->globaldatasize;
367 delete _GP(moduleInstFork)[i];
368 delete _GP(moduleInst)[i];
369 _GP(moduleInst)[i] = nullptr;
370 }
371
372 _GP(play).FreeProperties();
373 _GP(play).FreeViewportsAndCameras();
374
375 delete _G(roominstFork);
376 delete _G(roominst);
377 _G(roominstFork) = nullptr;
378 _G(roominst) = nullptr;
379
380 delete _G(dialogScriptsInst);
381 _G(dialogScriptsInst) = nullptr;
382
383 resetRoomStatuses();
384 _GP(troom).FreeScriptData();
385 _GP(troom).FreeProperties();
386 free_do_once_tokens();
387
388 // unregister gui controls from API exports
389 // TODO: find out why are we doing this here? is this really necessary?
390 for (int i = 0; i < _GP(game).numgui; ++i) {
391 unexport_gui_controls(i);
392 }
393 // Clear the managed object pool
394 ccUnregisterAllObjects();
395
396 // NOTE: channels are array of MAX_SOUND_CHANNELS+1 size
397 for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) {
398 stop_and_destroy_channel_ex(i, false);
399 }
400
401 clear_music_cache();
402 }
403
RestoreViewportsAndCameras(const RestoredData & r_data)404 void RestoreViewportsAndCameras(const RestoredData &r_data) {
405 for (size_t i = 0; i < r_data.Cameras.size(); ++i) {
406 const auto &cam_dat = r_data.Cameras[i];
407 auto cam = _GP(play).GetRoomCamera(i);
408 cam->SetID(cam_dat.ID);
409 if ((cam_dat.Flags & kSvgCamPosLocked) != 0)
410 cam->Lock();
411 else
412 cam->Release();
413 cam->SetAt(cam_dat.Left, cam_dat.Top);
414 cam->SetSize(Size(cam_dat.Width, cam_dat.Height));
415 }
416 for (size_t i = 0; i < r_data.Viewports.size(); ++i) {
417 const auto &view_dat = r_data.Viewports[i];
418 auto view = _GP(play).GetRoomViewport(i);
419 view->SetID(view_dat.ID);
420 view->SetVisible((view_dat.Flags & kSvgViewportVisible) != 0);
421 view->SetRect(RectWH(view_dat.Left, view_dat.Top, view_dat.Width, view_dat.Height));
422 view->SetZOrder(view_dat.ZOrder);
423 // Restore camera link
424 int cam_index = view_dat.CamID;
425 if (cam_index < 0) continue;
426 auto cam = _GP(play).GetRoomCamera(cam_index);
427 view->LinkCamera(cam);
428 cam->LinkToViewport(view);
429 }
430 _GP(play).InvalidateViewportZOrder();
431 }
432
433 // Final processing after successfully restoring from save
DoAfterRestore(const PreservedParams & pp,const RestoredData & r_data)434 HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data) {
435 // Use a yellow dialog highlight for older game versions
436 // CHECKME: it is dubious that this should be right here
437 if (_G(loaded_game_file_version) < kGameVersion_331)
438 _GP(play).dialog_options_highlight_color = DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT;
439
440 // Preserve whether the music vox is available
441 _GP(play).separate_music_lib = pp.MusicVOX;
442 // If they had the vox when they saved it, but they don't now
443 if ((pp.SpeechVOX < 0) && (_GP(play).want_speech >= 0))
444 _GP(play).want_speech = (-_GP(play).want_speech) - 1;
445 // If they didn't have the vox before, but now they do
446 else if ((pp.SpeechVOX >= 0) && (_GP(play).want_speech < 0))
447 _GP(play).want_speech = (-_GP(play).want_speech) - 1;
448
449 // Restore debug flags
450 if (_G(debug_flags) & DBG_DEBUGMODE)
451 _GP(play).debug_mode = 1;
452
453 // recache queued clips
454 for (int i = 0; i < _GP(play).new_music_queue_size; ++i) {
455 _GP(play).new_music_queue[i].cachedClip = nullptr;
456 }
457
458 // Remap old sound nums in case we restored a save having a different list of audio clips
459 RemapLegacySoundNums(_GP(game), _G(views), _G(loaded_game_file_version));
460
461 // restore these to the ones retrieved from the save game
462 const size_t dynsurf_num = Math::Min((uint)MAX_DYNAMIC_SURFACES, r_data.DynamicSurfaces.size());
463 for (size_t i = 0; i < dynsurf_num; ++i) {
464 _G(dynamicallyCreatedSurfaces)[i] = r_data.DynamicSurfaces[i];
465 }
466
467 for (int i = 0; i < _GP(game).numgui; ++i)
468 export_gui_controls(i);
469 update_gui_zorder();
470
471 if (create_global_script()) {
472 return new SavegameError(kSvgErr_GameObjectInitFailed,
473 String::FromFormat("Unable to recreate global script: %s", _G(ccErrorString).GetCStr()));
474 }
475
476 // read the global data into the newly created script
477 if (r_data.GlobalScript.Data.get())
478 memcpy(_G(gameinst)->globaldata, r_data.GlobalScript.Data.get(),
479 Math::Min((size_t)_G(gameinst)->globaldatasize, r_data.GlobalScript.Len));
480
481 // restore the script module data
482 for (int i = 0; i < _G(numScriptModules); ++i) {
483 if (r_data.ScriptModules[i].Data.get())
484 memcpy(_GP(moduleInst)[i]->globaldata, r_data.ScriptModules[i].Data.get(),
485 Math::Min((size_t)_GP(moduleInst)[i]->globaldatasize, r_data.ScriptModules[i].Len));
486 }
487
488 setup_player_character(_GP(game).playercharacter);
489
490 // Save some parameters to restore them after room load
491 int gstimer = _GP(play).gscript_timer;
492 int oldx1 = _GP(play).mboundx1, oldx2 = _GP(play).mboundx2;
493 int oldy1 = _GP(play).mboundy1, oldy2 = _GP(play).mboundy2;
494
495 // disable the queue momentarily
496 int queuedMusicSize = _GP(play).music_queue_size;
497 _GP(play).music_queue_size = 0;
498
499 update_polled_stuff_if_runtime();
500
501 // load the room the game was saved in
502 if (_G(displayed_room) >= 0)
503 load_new_room(_G(displayed_room), nullptr);
504
505 update_polled_stuff_if_runtime();
506
507 _GP(play).gscript_timer = gstimer;
508 // restore the correct room volume (they might have modified
509 // it with SetMusicVolume)
510 _GP(thisroom).Options.MusicVolume = r_data.RoomVolume;
511
512 _GP(mouse).SetMoveLimit(Rect(oldx1, oldy1, oldx2, oldy2));
513
514 set_cursor_mode(r_data.CursorMode);
515 set_mouse_cursor(r_data.CursorID);
516 if (r_data.CursorMode == MODE_USE)
517 SetActiveInventory(_G(playerchar)->activeinv);
518 // ensure that the current cursor is locked
519 _GP(spriteset).Precache(_GP(game).mcurs[r_data.CursorID].pic);
520
521 sys_window_set_title(_GP(play).game_name);
522
523 update_polled_stuff_if_runtime();
524
525 if (_G(displayed_room) >= 0) {
526 for (int i = 0; i < MAX_ROOM_BGFRAMES; ++i) {
527 if (r_data.RoomBkgScene[i]) {
528 _GP(thisroom).BgFrames[i].Graphic = r_data.RoomBkgScene[i];
529 }
530 }
531
532 _G(in_new_room) = 3; // don't run "enters screen" events
533 // now that room has loaded, copy saved light levels in
534 for (size_t i = 0; i < MAX_ROOM_REGIONS; ++i) {
535 _GP(thisroom).Regions[i].Light = r_data.RoomLightLevels[i];
536 _GP(thisroom).Regions[i].Tint = r_data.RoomTintLevels[i];
537 }
538 generate_light_table();
539
540 for (size_t i = 0; i < MAX_WALK_AREAS + 1; ++i) {
541 _GP(thisroom).WalkAreas[i].ScalingFar = r_data.RoomZoomLevels1[i];
542 _GP(thisroom).WalkAreas[i].ScalingNear = r_data.RoomZoomLevels2[i];
543 }
544
545 on_background_frame_change();
546 }
547
548 _G(gui_disabled_style) = convert_gui_disabled_style(_GP(game).options[OPT_DISABLEOFF]);
549
550 // restore the queue now that the music is playing
551 _GP(play).music_queue_size = queuedMusicSize;
552
553 if (_GP(play).digital_master_volume >= 0)
554 System_SetVolume(_GP(play).digital_master_volume);
555
556 // Run audio clips on channels
557 // these two crossfading parameters have to be temporarily reset
558 const int cf_in_chan = _GP(play).crossfading_in_channel;
559 const int cf_out_chan = _GP(play).crossfading_out_channel;
560 _GP(play).crossfading_in_channel = 0;
561 _GP(play).crossfading_out_channel = 0;
562
563 {
564 AudioChannelsLock lock;
565 // NOTE: channels are array of MAX_SOUND_CHANNELS+1 size
566 for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) {
567 const RestoredData::ChannelInfo &chan_info = r_data.AudioChans[i];
568 if (chan_info.ClipID < 0)
569 continue;
570 if ((size_t)chan_info.ClipID >= _GP(game).audioClips.size()) {
571 return new SavegameError(kSvgErr_GameObjectInitFailed,
572 String::FromFormat("Invalid audio clip index: %d (clip count: %zu).", chan_info.ClipID, _GP(game).audioClips.size()));
573 }
574 play_audio_clip_on_channel(i, &_GP(game).audioClips[chan_info.ClipID],
575 chan_info.Priority, chan_info.Repeat, chan_info.Pos);
576
577 auto *ch = lock.GetChannel(i);
578 if (ch != nullptr) {
579 ch->set_volume_direct(chan_info.VolAsPercent, chan_info.Vol);
580 ch->set_speed(chan_info.Speed);
581 ch->set_panning(chan_info.Pan);
582 ch->_panningAsPercentage = chan_info.PanAsPercent;
583 ch->_xSource = chan_info.XSource;
584 ch->_ySource = chan_info.YSource;
585 ch->_maximumPossibleDistanceAway = chan_info.MaxDist;
586 }
587 }
588 if ((cf_in_chan > 0) && (lock.GetChannel(cf_in_chan) != nullptr))
589 _GP(play).crossfading_in_channel = cf_in_chan;
590 if ((cf_out_chan > 0) && (lock.GetChannel(cf_out_chan) != nullptr))
591 _GP(play).crossfading_out_channel = cf_out_chan;
592
593 // If there were synced audio tracks, the time taken to load in the
594 // different channels will have thrown them out of sync, so re-time it
595 // NOTE: channels are array of MAX_SOUND_CHANNELS+1 size
596 for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) {
597 auto *ch = lock.GetChannelIfPlaying(i);
598 int pos = r_data.AudioChans[i].Pos;
599 if ((pos > 0) && (ch != nullptr)) {
600 ch->seek(pos);
601 }
602 }
603 } // -- AudioChannelsLock
604
605 // TODO: investigate loop range
606 for (int i = 1; i < MAX_SOUND_CHANNELS; ++i) {
607 if (r_data.DoAmbient[i])
608 PlayAmbientSound(i, r_data.DoAmbient[i], _GP(ambient)[i].vol, _GP(ambient)[i].x, _GP(ambient)[i].y);
609 }
610 update_directional_sound_vol();
611
612 for (int i = 0; i < _GP(game).numgui; ++i) {
613 _G(guibg)[i] = BitmapHelper::CreateBitmap(_GP(guis)[i].Width, _GP(guis)[i].Height, _GP(game).GetColorDepth());
614 _G(guibg)[i] = ReplaceBitmapWithSupportedFormat(_G(guibg)[i]);
615 }
616
617 recreate_overlay_ddbs();
618
619 GUI::MarkAllGUIForUpdate();
620
621 RestoreViewportsAndCameras(r_data);
622
623 _GP(play).ClearIgnoreInput(); // don't keep ignored input after save restore
624 update_polled_stuff_if_runtime();
625
626 pl_run_plugin_hooks(AGSE_POSTRESTOREGAME, 0);
627
628 if (_G(displayed_room) < 0) {
629 // the restart point, no room was loaded
630 load_new_room(_G(playerchar)->room, _G(playerchar));
631 _G(playerchar)->prevroom = -1;
632
633 first_room_initialization();
634 }
635
636 if ((_GP(play).music_queue_size > 0) && (_G(cachedQueuedMusic) == nullptr)) {
637 _G(cachedQueuedMusic) = load_music_from_disk(_GP(play).music_queue[0], 0);
638 }
639
640 // Test if the old-style audio had playing music and it was properly loaded
641 if (_G(current_music_type) > 0) {
642 AudioChannelsLock lock;
643
644 if ((_G(crossFading) > 0 && !lock.GetChannelIfPlaying(_G(crossFading))) ||
645 (_G(crossFading) <= 0 && !lock.GetChannelIfPlaying(SCHAN_MUSIC))) {
646 _G(current_music_type) = 0; // playback failed, reset flag
647 }
648 }
649
650 set_game_speed(r_data.FPS);
651
652 return HSaveError::None();
653 }
654
RestoreGameState(Stream * in,SavegameVersion svg_version)655 HSaveError RestoreGameState(Stream *in, SavegameVersion svg_version) {
656 PreservedParams pp;
657 RestoredData r_data;
658 DoBeforeRestore(pp);
659 HSaveError err;
660 if (svg_version >= kSvgVersion_Components)
661 err = SavegameComponents::ReadAll(in, svg_version, pp, r_data);
662 else
663 err = restore_game_data(in, svg_version, pp, r_data);
664 if (!err)
665 return err;
666 return DoAfterRestore(pp, r_data);
667 }
668
669
WriteSaveImage(Stream * out,const Bitmap * screenshot)670 void WriteSaveImage(Stream *out, const Bitmap *screenshot) {
671 // store the screenshot at the start to make it easily accesible
672 out->WriteInt32((screenshot == nullptr) ? 0 : 1);
673
674 if (screenshot)
675 serialize_bitmap(screenshot, out);
676 }
677
WriteDescription(Stream * out,const String & user_text,const Bitmap * user_image)678 void WriteDescription(Stream *out, const String &user_text, const Bitmap *user_image) {
679 // Data format version
680 out->WriteInt32(kSvgVersion_Current);
681 soff_t env_pos = out->GetPosition();
682 out->WriteInt32(0);
683 // Enviroment information
684 StrUtil::WriteString("Adventure Game Studio run-time engine", out);
685 StrUtil::WriteString(_G(EngineVersion).LongString, out);
686 StrUtil::WriteString(_GP(game).guid, out);
687 StrUtil::WriteString(_GP(game).gamename, out);
688 StrUtil::WriteString(_GP(ResPaths).GamePak.Name, out);
689 out->WriteInt32(_G(loaded_game_file_version));
690 out->WriteInt32(_GP(game).GetColorDepth());
691 out->WriteInt32(_GP(game).uniqueid);
692 soff_t env_end_pos = out->GetPosition();
693 out->Seek(env_pos, kSeekBegin);
694 out->WriteInt32(env_end_pos - env_pos);
695 out->Seek(env_end_pos, kSeekBegin);
696 // User description
697 StrUtil::WriteString(user_text, out);
698 WriteSaveImage(out, user_image);
699 }
700
StartSavegame(const String & filename,const String & user_text,const Bitmap * user_image)701 Stream *StartSavegame(const String &filename, const String &user_text, const Bitmap *user_image) {
702 Stream *out = Shared::File::CreateFile(filename);
703 if (!out)
704 return nullptr;
705
706 // Initialize and write Vista header
707 RICH_GAME_MEDIA_HEADER vistaHeader;
708 memset(&vistaHeader, 0, sizeof(RICH_GAME_MEDIA_HEADER));
709 vistaHeader.dwMagicNumber = RM_MAGICNUMBER;
710 vistaHeader.dwHeaderVersion = 1;
711 vistaHeader.dwHeaderSize = sizeof(RICH_GAME_MEDIA_HEADER);
712 vistaHeader.dwThumbnailOffsetHigherDword = 0;
713 vistaHeader.dwThumbnailOffsetLowerDword = 0;
714 vistaHeader.dwThumbnailSize = 0;
715 convert_guid_from_text_to_binary(_GP(game).guid, &vistaHeader.guidGameId[0]);
716
717 vistaHeader.setSaveName(user_text);
718
719 vistaHeader.szLevelName[0] = 0;
720 vistaHeader.szComments[0] = 0;
721 // MS Windows Vista rich media header
722 vistaHeader.WriteToFile(out);
723
724 // Savegame signature
725 out->Write(SavegameSource::Signature, strlen(SavegameSource::Signature));
726
727 // CHECKME: what is this plugin hook suppose to mean, and if it is called here correctly
728 pl_run_plugin_hooks(AGSE_PRESAVEGAME, 0);
729
730 // Write descrition block
731 WriteDescription(out, user_text, user_image);
732 return out;
733 }
734
DoBeforeSave()735 void DoBeforeSave() {
736 if (_GP(play).cur_music_number >= 0) {
737 if (IsMusicPlaying() == 0)
738 _GP(play).cur_music_number = -1;
739 }
740
741 if (_G(displayed_room) >= 0) {
742 // update the current room script's data segment copy
743 if (_G(roominst))
744 save_room_data_segment();
745
746 // Update the saved interaction variable values
747 for (size_t i = 0; i < _GP(thisroom).LocalVariables.size() && i < (size_t)MAX_GLOBAL_VARIABLES; ++i)
748 _G(croom)->interactionVariableValues[i] = _GP(thisroom).LocalVariables[i].Value;
749 }
750 }
751
SaveGameState(Stream * out)752 void SaveGameState(Stream *out) {
753 DoBeforeSave();
754 SavegameComponents::WriteAllCommon(out);
755 }
756
757 } // namespace Engine
758 } // namespace AGS
759 } // namespace AGS3
760