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