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/lib/std/map.h"
24 #include "ags/shared/ac/audio_clip_type.h"
25 #include "ags/engine/ac/character.h"
26 #include "ags/shared/ac/common.h"
27 #include "ags/shared/ac/dialog_topic.h"
28 #include "ags/engine/ac/draw.h"
29 #include "ags/engine/ac/dynamic_sprite.h"
30 #include "ags/engine/ac/game.h"
31 #include "ags/shared/ac/game_setup_struct.h"
32 #include "ags/engine/ac/game_state.h"
33 #include "ags/engine/ac/gui.h"
34 #include "ags/engine/ac/mouse.h"
35 #include "ags/engine/ac/move_list.h"
36 #include "ags/engine/ac/overlay.h"
37 #include "ags/engine/ac/room_status.h"
38 #include "ags/engine/ac/screen_overlay.h"
39 #include "ags/shared/ac/sprite_cache.h"
40 #include "ags/shared/ac/view.h"
41 #include "ags/engine/ac/system.h"
42 #include "ags/engine/ac/dynobj/cc_serializer.h"
43 #include "ags/shared/debugging/out.h"
44 #include "ags/engine/game/savegame_components.h"
45 #include "ags/engine/game/savegame_internal.h"
46 #include "ags/shared/gfx/bitmap.h"
47 #include "ags/engine/gui/animating_gui_button.h"
48 #include "ags/shared/gui/gui_button.h"
49 #include "ags/shared/gui/gui_inv.h"
50 #include "ags/shared/gui/gui_label.h"
51 #include "ags/shared/gui/gui_listbox.h"
52 #include "ags/shared/gui/gui_main.h"
53 #include "ags/shared/gui/gui_slider.h"
54 #include "ags/shared/gui/gui_textbox.h"
55 #include "ags/plugins/ags_plugin.h"
56 #include "ags/plugins/plugin_engine.h"
57 #include "ags/shared/script/cc_error.h"
58 #include "ags/engine/script/script.h"
59 #include "ags/shared/util/file_stream.h" // TODO: needed only because plugins expect file handle
60 #include "ags/engine/media/audio/audio_system.h"
61
62 namespace AGS3 {
63
64 using namespace Shared;
65
66 namespace AGS {
67 namespace Engine {
68
69 namespace SavegameComponents {
70
71 const String ComponentListTag = "Components";
72
WriteFormatTag(Stream * out,const String & tag,bool open=true)73 void WriteFormatTag(Stream *out, const String &tag, bool open = true) {
74 String full_tag = String::FromFormat(open ? "<%s>" : "</%s>", tag.GetCStr());
75 out->Write(full_tag.GetCStr(), full_tag.GetLength());
76 }
77
ReadFormatTag(Stream * in,String & tag,bool open=true)78 bool ReadFormatTag(Stream *in, String &tag, bool open = true) {
79 if (in->ReadByte() != '<')
80 return false;
81 if (!open && in->ReadByte() != '/')
82 return false;
83 tag.Empty();
84 while (!in->EOS()) {
85 char c = in->ReadByte();
86 if (c == '>')
87 return true;
88 tag.AppendChar(c);
89 }
90 return false; // reached EOS before closing symbol
91 }
92
AssertFormatTag(Stream * in,const String & tag,bool open=true)93 bool AssertFormatTag(Stream *in, const String &tag, bool open = true) {
94 String read_tag;
95 if (!ReadFormatTag(in, read_tag, open))
96 return false;
97 return read_tag.Compare(tag) == 0;
98 }
99
AssertFormatTagStrict(HSaveError & err,Stream * in,const String & tag,bool open=true)100 bool AssertFormatTagStrict(HSaveError &err, Stream *in, const String &tag, bool open = true) {
101
102 String read_tag;
103 if (!ReadFormatTag(in, read_tag, open) || read_tag.Compare(tag) != 0) {
104 err = new SavegameError(kSvgErr_InconsistentFormat,
105 String::FromFormat("Mismatching tag: %s.", tag.GetCStr()));
106 return false;
107 }
108 return true;
109 }
110
AssertCompatLimit(HSaveError & err,int count,int max_count,const char * content_name)111 inline bool AssertCompatLimit(HSaveError &err, int count, int max_count, const char *content_name) {
112 if (count > max_count) {
113 err = new SavegameError(kSvgErr_IncompatibleEngine,
114 String::FromFormat("Incompatible number of %s (count: %d, max: %d).",
115 content_name, count, max_count));
116 return false;
117 }
118 return true;
119 }
120
AssertCompatRange(HSaveError & err,int value,int min_value,int max_value,const char * content_name)121 inline bool AssertCompatRange(HSaveError &err, int value, int min_value, int max_value, const char *content_name) {
122 if (value < min_value || value > max_value) {
123 err = new SavegameError(kSvgErr_IncompatibleEngine,
124 String::FromFormat("Restore game error: incompatible %s (id: %d, range: %d - %d).",
125 content_name, value, min_value, max_value));
126 return false;
127 }
128 return true;
129 }
130
AssertGameContent(HSaveError & err,int new_val,int original_val,const char * content_name)131 inline bool AssertGameContent(HSaveError &err, int new_val, int original_val, const char *content_name) {
132 if (new_val != original_val) {
133 err = new SavegameError(kSvgErr_GameContentAssertion,
134 String::FromFormat("Mismatching number of %s (game: %d, save: %d).",
135 content_name, original_val, new_val));
136 return false;
137 }
138 return true;
139 }
140
AssertGameObjectContent(HSaveError & err,int new_val,int original_val,const char * content_name,const char * obj_type,int obj_id)141 inline bool AssertGameObjectContent(HSaveError &err, int new_val, int original_val, const char *content_name,
142 const char *obj_type, int obj_id) {
143 if (new_val != original_val) {
144 err = new SavegameError(kSvgErr_GameContentAssertion,
145 String::FromFormat("Mismatching number of %s, %s #%d (game: %d, save: %d).",
146 content_name, obj_type, obj_id, original_val, new_val));
147 return false;
148 }
149 return true;
150 }
151
AssertGameObjectContent2(HSaveError & err,int new_val,int original_val,const char * content_name,const char * obj1_type,int obj1_id,const char * obj2_type,int obj2_id)152 inline bool AssertGameObjectContent2(HSaveError &err, int new_val, int original_val, const char *content_name,
153 const char *obj1_type, int obj1_id, const char *obj2_type, int obj2_id) {
154 if (new_val != original_val) {
155 err = new SavegameError(kSvgErr_GameContentAssertion,
156 String::FromFormat("Mismatching number of %s, %s #%d, %s #%d (game: %d, save: %d).",
157 content_name, obj1_type, obj1_id, obj2_type, obj2_id, original_val, new_val));
158 return false;
159 }
160 return true;
161 }
162
163
WriteCameraState(const Camera & cam,Stream * out)164 void WriteCameraState(const Camera &cam, Stream *out) {
165 int flags = 0;
166 if (cam.IsLocked()) flags |= kSvgCamPosLocked;
167 out->WriteInt32(flags);
168 const Rect &rc = cam.GetRect();
169 out->WriteInt32(rc.Left);
170 out->WriteInt32(rc.Top);
171 out->WriteInt32(rc.GetWidth());
172 out->WriteInt32(rc.GetHeight());
173 }
174
WriteViewportState(const Viewport & view,Stream * out)175 void WriteViewportState(const Viewport &view, Stream *out) {
176 int flags = 0;
177 if (view.IsVisible()) flags |= kSvgViewportVisible;
178 out->WriteInt32(flags);
179 const Rect &rc = view.GetRect();
180 out->WriteInt32(rc.Left);
181 out->WriteInt32(rc.Top);
182 out->WriteInt32(rc.GetWidth());
183 out->WriteInt32(rc.GetHeight());
184 out->WriteInt32(view.GetZOrder());
185 auto cam = view.GetCamera();
186 if (cam)
187 out->WriteInt32(cam->GetID());
188 else
189 out->WriteInt32(-1);
190 }
191
WriteGameState(Stream * out)192 HSaveError WriteGameState(Stream *out) {
193 // Game base
194 _GP(game).WriteForSavegame(out);
195 // Game palette
196 // TODO: probably no need to save this for hi/true-res game
197 out->WriteArray(_G(palette), sizeof(RGB), 256);
198
199 if (_G(loaded_game_file_version) <= kGameVersion_272) {
200 // Global variables
201 out->WriteInt32(_G(numGlobalVars));
202 for (int i = 0; i < _G(numGlobalVars); ++i)
203 _G(globalvars)[i].Write(out);
204 }
205
206 // Game state
207 _GP(play).WriteForSavegame(out);
208 // Other dynamic values
209 out->WriteInt32(_G(frames_per_second));
210 out->WriteInt32(_G(loopcounter));
211 out->WriteInt32(_G(ifacepopped));
212 out->WriteInt32(_G(game_paused));
213 // Mouse cursor
214 out->WriteInt32(_G(cur_mode));
215 out->WriteInt32(_G(cur_cursor));
216 out->WriteInt32(_G(mouse_on_iface));
217
218 // Viewports and cameras
219 int viewcam_flags = 0;
220 if (_GP(play).IsAutoRoomViewport())
221 viewcam_flags |= kSvgGameAutoRoomView;
222 out->WriteInt32(viewcam_flags);
223 out->WriteInt32(_GP(play).GetRoomCameraCount());
224 for (int i = 0; i < _GP(play).GetRoomCameraCount(); ++i)
225 WriteCameraState(*_GP(play).GetRoomCamera(i), out);
226 out->WriteInt32(_GP(play).GetRoomViewportCount());
227 for (int i = 0; i < _GP(play).GetRoomViewportCount(); ++i)
228 WriteViewportState(*_GP(play).GetRoomViewport(i), out);
229
230 return HSaveError::None();
231 }
232
ReadLegacyCameraState(Stream * in,RestoredData & r_data)233 void ReadLegacyCameraState(Stream *in, RestoredData &r_data) {
234 // Precreate viewport and camera and save data in temp structs
235 int camx = in->ReadInt32();
236 int camy = in->ReadInt32();
237 _GP(play).CreateRoomCamera();
238 _GP(play).CreateRoomViewport();
239 const auto &main_view = _GP(play).GetMainViewport();
240 RestoredData::CameraData cam_dat;
241 cam_dat.ID = 0;
242 cam_dat.Left = camx;
243 cam_dat.Top = camy;
244 cam_dat.Width = main_view.GetWidth();
245 cam_dat.Height = main_view.GetHeight();
246 r_data.Cameras.push_back(cam_dat);
247 RestoredData::ViewportData view_dat;
248 view_dat.ID = 0;
249 view_dat.Width = main_view.GetWidth();
250 view_dat.Height = main_view.GetHeight();
251 view_dat.Flags = kSvgViewportVisible;
252 view_dat.CamID = 0;
253 r_data.Viewports.push_back(view_dat);
254 }
255
ReadCameraState(RestoredData & r_data,Stream * in)256 void ReadCameraState(RestoredData &r_data, Stream *in) {
257 RestoredData::CameraData cam;
258 cam.ID = r_data.Cameras.size();
259 cam.Flags = in->ReadInt32();
260 cam.Left = in->ReadInt32();
261 cam.Top = in->ReadInt32();
262 cam.Width = in->ReadInt32();
263 cam.Height = in->ReadInt32();
264 r_data.Cameras.push_back(cam);
265 }
266
ReadViewportState(RestoredData & r_data,Stream * in)267 void ReadViewportState(RestoredData &r_data, Stream *in) {
268 RestoredData::ViewportData view;
269 view.ID = r_data.Viewports.size();
270 view.Flags = in->ReadInt32();
271 view.Left = in->ReadInt32();
272 view.Top = in->ReadInt32();
273 view.Width = in->ReadInt32();
274 view.Height = in->ReadInt32();
275 view.ZOrder = in->ReadInt32();
276 view.CamID = in->ReadInt32();
277 r_data.Viewports.push_back(view);
278 }
279
ReadGameState(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)280 HSaveError ReadGameState(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
281 HSaveError err;
282 GameStateSvgVersion svg_ver = (GameStateSvgVersion)cmp_ver;
283 // Game base
284 _GP(game).ReadFromSavegame(in);
285 // Game palette
286 in->ReadArray(_G(palette), sizeof(RGB), 256);
287
288 if (_G(loaded_game_file_version) <= kGameVersion_272) {
289 // Legacy interaction global variables
290 if (!AssertGameContent(err, in->ReadInt32(), _G(numGlobalVars), "Global Variables"))
291 return err;
292 for (int i = 0; i < _G(numGlobalVars); ++i)
293 _G(globalvars)[i].Read(in);
294 }
295
296 // Game state
297 _GP(play).ReadFromSavegame(in, svg_ver, r_data);
298
299 // Other dynamic values
300 r_data.FPS = in->ReadInt32();
301 set_loop_counter(in->ReadInt32());
302 _G(ifacepopped) = in->ReadInt32();
303 _G(game_paused) = in->ReadInt32();
304 // Mouse cursor state
305 r_data.CursorMode = in->ReadInt32();
306 r_data.CursorID = in->ReadInt32();
307 _G(mouse_on_iface) = in->ReadInt32();
308
309 // Viewports and cameras
310 if (svg_ver < kGSSvgVersion_350_10) {
311 ReadLegacyCameraState(in, r_data);
312 r_data.Cameras[0].Flags = r_data.Camera0_Flags;
313 } else {
314 int viewcam_flags = in->ReadInt32();
315 _GP(play).SetAutoRoomViewport((viewcam_flags & kSvgGameAutoRoomView) != 0);
316 // TODO: we create viewport and camera objects here because they are
317 // required for the managed pool deserialization, but read actual
318 // data into temp structs because we need to apply it after active
319 // room is loaded.
320 // See comments to RestoredData struct for further details.
321 int cam_count = in->ReadInt32();
322 for (int i = 0; i < cam_count; ++i) {
323 _GP(play).CreateRoomCamera();
324 ReadCameraState(r_data, in);
325 }
326 int view_count = in->ReadInt32();
327 for (int i = 0; i < view_count; ++i) {
328 _GP(play).CreateRoomViewport();
329 ReadViewportState(r_data, in);
330 }
331 }
332 return err;
333 }
334
WriteAudio(Stream * out)335 HSaveError WriteAudio(Stream *out) {
336 AudioChannelsLock lock;
337
338 // Game content assertion
339 out->WriteInt32(_GP(game).audioClipTypes.size());
340 out->WriteInt32(_GP(game).audioClips.size()); // [ivan-mogilko] not necessary, kept only to avoid changing save format
341 // Audio types
342 for (size_t i = 0; i < _GP(game).audioClipTypes.size(); ++i) {
343 _GP(game).audioClipTypes[i].WriteToSavegame(out);
344 out->WriteInt32(_GP(play).default_audio_type_volumes[i]);
345 }
346
347 // Audio clips and crossfade
348 for (int i = 0; i <= MAX_SOUND_CHANNELS; i++) {
349 auto *ch = lock.GetChannelIfPlaying(i);
350 if ((ch != nullptr) && (ch->_sourceClip != nullptr)) {
351 out->WriteInt32(((ScriptAudioClip *)ch->_sourceClip)->id);
352 out->WriteInt32(ch->get_pos());
353 out->WriteInt32(ch->_priority);
354 out->WriteInt32(ch->_repeat ? 1 : 0);
355 out->WriteInt32(ch->_vol);
356 out->WriteInt32(ch->_panning);
357 out->WriteInt32(ch->_volAsPercentage);
358 out->WriteInt32(ch->_panningAsPercentage);
359 out->WriteInt32(ch->get_speed());
360 // since version 1
361 out->WriteInt32(ch->_xSource);
362 out->WriteInt32(ch->_ySource);
363 out->WriteInt32(ch->_maximumPossibleDistanceAway);
364 } else {
365 out->WriteInt32(-1);
366 }
367 }
368 out->WriteInt32(_G(crossFading));
369 out->WriteInt32(_G(crossFadeVolumePerStep));
370 out->WriteInt32(_G(crossFadeStep));
371 out->WriteInt32(_G(crossFadeVolumeAtStart));
372 // CHECKME: why this needs to be saved?
373 out->WriteInt32(_G(current_music_type));
374
375 // Ambient sound
376 for (int i = 0; i < MAX_SOUND_CHANNELS; ++i)
377 _GP(ambient)[i].WriteToFile(out);
378 return HSaveError::None();
379 }
380
ReadAudio(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)381 HSaveError ReadAudio(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
382 HSaveError err;
383 // Game content assertion
384 if (!AssertGameContent(err, in->ReadInt32(), _GP(game).audioClipTypes.size(), "Audio Clip Types"))
385 return err;
386 in->ReadInt32(); // audio clip count
387 /* [ivan-mogilko] looks like it's not necessary to assert, as there's no data serialized for clips
388 if (!AssertGameContent(err, in->ReadInt32(), _GP(game).audioClips.size(), "Audio Clips"))
389 return err;*/
390
391 // Audio types
392 for (size_t i = 0; i < _GP(game).audioClipTypes.size(); ++i) {
393 _GP(game).audioClipTypes[i].ReadFromSavegame(in);
394 _GP(play).default_audio_type_volumes[i] = in->ReadInt32();
395 }
396
397 // Audio clips and crossfade
398 for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) {
399 RestoredData::ChannelInfo &chan_info = r_data.AudioChans[i];
400 chan_info.Pos = 0;
401 chan_info.ClipID = in->ReadInt32();
402 if (chan_info.ClipID >= 0) {
403 chan_info.Pos = in->ReadInt32();
404 if (chan_info.Pos < 0)
405 chan_info.Pos = 0;
406 chan_info.Priority = in->ReadInt32();
407 chan_info.Repeat = in->ReadInt32();
408 chan_info.Vol = in->ReadInt32();
409 chan_info.Pan = in->ReadInt32();
410 chan_info.VolAsPercent = in->ReadInt32();
411 chan_info.PanAsPercent = in->ReadInt32();
412 chan_info.Speed = 1000;
413 chan_info.Speed = in->ReadInt32();
414 if (cmp_ver >= 1) {
415 chan_info.XSource = in->ReadInt32();
416 chan_info.YSource = in->ReadInt32();
417 chan_info.MaxDist = in->ReadInt32();
418 }
419 }
420 }
421 _G(crossFading) = in->ReadInt32();
422 _G(crossFadeVolumePerStep) = in->ReadInt32();
423 _G(crossFadeStep) = in->ReadInt32();
424 _G(crossFadeVolumeAtStart) = in->ReadInt32();
425 // preserve legacy music type setting
426 _G(current_music_type) = in->ReadInt32();
427
428 // Ambient sound
429 for (int i = 0; i < MAX_SOUND_CHANNELS; ++i)
430 _GP(ambient)[i].ReadFromFile(in);
431 for (int i = 1; i < MAX_SOUND_CHANNELS; ++i) {
432 if (_GP(ambient)[i].channel == 0) {
433 r_data.DoAmbient[i] = 0;
434 } else {
435 r_data.DoAmbient[i] = _GP(ambient)[i].num;
436 _GP(ambient)[i].channel = 0;
437 }
438 }
439 return err;
440 }
441
WriteTimesRun272(const Interaction & intr,Stream * out)442 void WriteTimesRun272(const Interaction &intr, Stream *out) {
443 for (size_t i = 0; i < intr.Events.size(); ++i)
444 out->WriteInt32(intr.Events[i].TimesRun);
445 }
446
WriteInteraction272(const Interaction & intr,Stream * out)447 void WriteInteraction272(const Interaction &intr, Stream *out) {
448 const size_t evt_count = intr.Events.size();
449 out->WriteInt32(evt_count);
450 for (size_t i = 0; i < evt_count; ++i)
451 out->WriteInt32(intr.Events[i].Type);
452 WriteTimesRun272(intr, out);
453 }
454
ReadTimesRun272(Interaction & intr,Stream * in)455 void ReadTimesRun272(Interaction &intr, Stream *in) {
456 for (size_t i = 0; i < intr.Events.size(); ++i)
457 intr.Events[i].TimesRun = in->ReadInt32();
458 }
459
ReadInteraction272(Interaction & intr,Stream * in)460 HSaveError ReadInteraction272(Interaction &intr, Stream *in) {
461 HSaveError err;
462 const size_t evt_count = in->ReadInt32();
463 if (!AssertCompatLimit(err, evt_count, MAX_NEWINTERACTION_EVENTS, "interactions"))
464 return err;
465 intr.Events.resize(evt_count);
466 for (size_t i = 0; i < evt_count; ++i)
467 intr.Events[i].Type = in->ReadInt32();
468 ReadTimesRun272(intr, in);
469 return err;
470 }
471
WriteCharacters(Stream * out)472 HSaveError WriteCharacters(Stream *out) {
473 out->WriteInt32(_GP(game).numcharacters);
474 for (int i = 0; i < _GP(game).numcharacters; ++i) {
475 _GP(game).chars[i].WriteToFile(out);
476 _G(charextra)[i].WriteToFile(out);
477 Properties::WriteValues(_GP(play).charProps[i], out);
478 if (_G(loaded_game_file_version) <= kGameVersion_272)
479 WriteTimesRun272(*_GP(game).intrChar[i], out);
480 // character movement path cache
481 _G(mls)[CHMLSOFFS + i].WriteToFile(out);
482 }
483 return HSaveError::None();
484 }
485
ReadCharacters(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)486 HSaveError ReadCharacters(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
487 HSaveError err;
488 if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcharacters, "Characters"))
489 return err;
490 for (int i = 0; i < _GP(game).numcharacters; ++i) {
491 _GP(game).chars[i].ReadFromFile(in);
492 _G(charextra)[i].ReadFromFile(in);
493 Properties::ReadValues(_GP(play).charProps[i], in);
494 if (_G(loaded_game_file_version) <= kGameVersion_272)
495 ReadTimesRun272(*_GP(game).intrChar[i], in);
496 // character movement path cache
497 err = _G(mls)[CHMLSOFFS + i].ReadFromFile(in, cmp_ver > 0 ? 1 : 0);
498 if (!err)
499 return err;
500 }
501 return err;
502 }
503
WriteDialogs(Stream * out)504 HSaveError WriteDialogs(Stream *out) {
505 out->WriteInt32(_GP(game).numdialog);
506 for (int i = 0; i < _GP(game).numdialog; ++i) {
507 _G(dialog)[i].WriteToSavegame(out);
508 }
509 return HSaveError::None();
510 }
511
ReadDialogs(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)512 HSaveError ReadDialogs(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
513 HSaveError err;
514 if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numdialog, "Dialogs"))
515 return err;
516 for (int i = 0; i < _GP(game).numdialog; ++i) {
517 _G(dialog)[i].ReadFromSavegame(in);
518 }
519 return err;
520 }
521
WriteGUI(Stream * out)522 HSaveError WriteGUI(Stream *out) {
523 // GUI state
524 WriteFormatTag(out, "GUIs");
525 out->WriteInt32(_GP(game).numgui);
526 for (int i = 0; i < _GP(game).numgui; ++i)
527 _GP(guis)[i].WriteToSavegame(out);
528
529 WriteFormatTag(out, "GUIButtons");
530 out->WriteInt32(_G(numguibuts));
531 for (int i = 0; i < _G(numguibuts); ++i)
532 _GP(guibuts)[i].WriteToSavegame(out);
533
534 WriteFormatTag(out, "GUILabels");
535 out->WriteInt32(_G(numguilabels));
536 for (int i = 0; i < _G(numguilabels); ++i)
537 _GP(guilabels)[i].WriteToSavegame(out);
538
539 WriteFormatTag(out, "GUIInvWindows");
540 out->WriteInt32(_G(numguiinv));
541 for (int i = 0; i < _G(numguiinv); ++i)
542 _GP(guiinv)[i].WriteToSavegame(out);
543
544 WriteFormatTag(out, "GUISliders");
545 out->WriteInt32(_G(numguislider));
546 for (int i = 0; i < _G(numguislider); ++i)
547 _GP(guislider)[i].WriteToSavegame(out);
548
549 WriteFormatTag(out, "GUITextBoxes");
550 out->WriteInt32(_G(numguitext));
551 for (int i = 0; i < _G(numguitext); ++i)
552 _GP(guitext)[i].WriteToSavegame(out);
553
554 WriteFormatTag(out, "GUIListBoxes");
555 out->WriteInt32(_G(numguilist));
556 for (int i = 0; i < _G(numguilist); ++i)
557 _GP(guilist)[i].WriteToSavegame(out);
558
559 // Animated buttons
560 WriteFormatTag(out, "AnimatedButtons");
561 out->WriteInt32(_G(numAnimButs));
562 for (int i = 0; i < _G(numAnimButs); ++i)
563 _G(animbuts)[i].WriteToFile(out);
564 return HSaveError::None();
565 }
566
ReadGUI(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)567 HSaveError ReadGUI(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
568 HSaveError err;
569 const GuiSvgVersion svg_ver = (GuiSvgVersion)cmp_ver;
570 // GUI state
571 if (!AssertFormatTagStrict(err, in, "GUIs"))
572 return err;
573 if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numgui, "GUIs"))
574 return err;
575 for (int i = 0; i < _GP(game).numgui; ++i)
576 _GP(guis)[i].ReadFromSavegame(in, svg_ver);
577
578 if (!AssertFormatTagStrict(err, in, "GUIButtons"))
579 return err;
580 if (!AssertGameContent(err, in->ReadInt32(), _G(numguibuts), "GUI Buttons"))
581 return err;
582 for (int i = 0; i < _G(numguibuts); ++i)
583 _GP(guibuts)[i].ReadFromSavegame(in, svg_ver);
584
585 if (!AssertFormatTagStrict(err, in, "GUILabels"))
586 return err;
587 if (!AssertGameContent(err, in->ReadInt32(), _G(numguilabels), "GUI Labels"))
588 return err;
589 for (int i = 0; i < _G(numguilabels); ++i)
590 _GP(guilabels)[i].ReadFromSavegame(in, svg_ver);
591
592 if (!AssertFormatTagStrict(err, in, "GUIInvWindows"))
593 return err;
594 if (!AssertGameContent(err, in->ReadInt32(), _G(numguiinv), "GUI InvWindows"))
595 return err;
596 for (int i = 0; i < _G(numguiinv); ++i)
597 _GP(guiinv)[i].ReadFromSavegame(in, svg_ver);
598
599 if (!AssertFormatTagStrict(err, in, "GUISliders"))
600 return err;
601 if (!AssertGameContent(err, in->ReadInt32(), _G(numguislider), "GUI Sliders"))
602 return err;
603 for (int i = 0; i < _G(numguislider); ++i)
604 _GP(guislider)[i].ReadFromSavegame(in, svg_ver);
605
606 if (!AssertFormatTagStrict(err, in, "GUITextBoxes"))
607 return err;
608 if (!AssertGameContent(err, in->ReadInt32(), _G(numguitext), "GUI TextBoxes"))
609 return err;
610 for (int i = 0; i < _G(numguitext); ++i)
611 _GP(guitext)[i].ReadFromSavegame(in, svg_ver);
612
613 if (!AssertFormatTagStrict(err, in, "GUIListBoxes"))
614 return err;
615 if (!AssertGameContent(err, in->ReadInt32(), _G(numguilist), "GUI ListBoxes"))
616 return err;
617 for (int i = 0; i < _G(numguilist); ++i)
618 _GP(guilist)[i].ReadFromSavegame(in, svg_ver);
619
620 // Animated buttons
621 if (!AssertFormatTagStrict(err, in, "AnimatedButtons"))
622 return err;
623 int anim_count = in->ReadInt32();
624 if (!AssertCompatLimit(err, anim_count, MAX_ANIMATING_BUTTONS, "animated buttons"))
625 return err;
626 _G(numAnimButs) = anim_count;
627 for (int i = 0; i < _G(numAnimButs); ++i)
628 _G(animbuts)[i].ReadFromFile(in);
629 return err;
630 }
631
WriteInventory(Stream * out)632 HSaveError WriteInventory(Stream *out) {
633 out->WriteInt32(_GP(game).numinvitems);
634 for (int i = 0; i < _GP(game).numinvitems; ++i) {
635 _GP(game).invinfo[i].WriteToSavegame(out);
636 Properties::WriteValues(_GP(play).invProps[i], out);
637 if (_G(loaded_game_file_version) <= kGameVersion_272)
638 WriteTimesRun272(*_GP(game).intrInv[i], out);
639 }
640 return HSaveError::None();
641 }
642
ReadInventory(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)643 HSaveError ReadInventory(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
644 HSaveError err;
645 if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numinvitems, "Inventory Items"))
646 return err;
647 for (int i = 0; i < _GP(game).numinvitems; ++i) {
648 _GP(game).invinfo[i].ReadFromSavegame(in);
649 Properties::ReadValues(_GP(play).invProps[i], in);
650 if (_G(loaded_game_file_version) <= kGameVersion_272)
651 ReadTimesRun272(*_GP(game).intrInv[i], in);
652 }
653 return err;
654 }
655
WriteMouseCursors(Stream * out)656 HSaveError WriteMouseCursors(Stream *out) {
657 out->WriteInt32(_GP(game).numcursors);
658 for (int i = 0; i < _GP(game).numcursors; ++i) {
659 _GP(game).mcurs[i].WriteToSavegame(out);
660 }
661 return HSaveError::None();
662 }
663
ReadMouseCursors(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)664 HSaveError ReadMouseCursors(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
665 HSaveError err;
666 if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numcursors, "Mouse Cursors"))
667 return err;
668 for (int i = 0; i < _GP(game).numcursors; ++i) {
669 _GP(game).mcurs[i].ReadFromSavegame(in);
670 }
671 return err;
672 }
673
WriteViews(Stream * out)674 HSaveError WriteViews(Stream *out) {
675 out->WriteInt32(_GP(game).numviews);
676 for (int view = 0; view < _GP(game).numviews; ++view) {
677 out->WriteInt32(_G(views)[view].numLoops);
678 for (int loop = 0; loop < _G(views)[view].numLoops; ++loop) {
679 out->WriteInt32(_G(views)[view].loops[loop].numFrames);
680 for (int frame = 0; frame < _G(views)[view].loops[loop].numFrames; ++frame) {
681 out->WriteInt32(_G(views)[view].loops[loop].frames[frame].sound);
682 out->WriteInt32(_G(views)[view].loops[loop].frames[frame].pic);
683 }
684 }
685 }
686 return HSaveError::None();
687 }
688
ReadViews(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)689 HSaveError ReadViews(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
690 HSaveError err;
691 if (!AssertGameContent(err, in->ReadInt32(), _GP(game).numviews, "Views"))
692 return err;
693 for (int view = 0; view < _GP(game).numviews; ++view) {
694 if (!AssertGameObjectContent(err, in->ReadInt32(), _G(views)[view].numLoops,
695 "Loops", "View", view))
696 return err;
697 for (int loop = 0; loop < _G(views)[view].numLoops; ++loop) {
698 if (!AssertGameObjectContent2(err, in->ReadInt32(), _G(views)[view].loops[loop].numFrames,
699 "Frame", "View", view, "Loop", loop))
700 return err;
701 for (int frame = 0; frame < _G(views)[view].loops[loop].numFrames; ++frame) {
702 _G(views)[view].loops[loop].frames[frame].sound = in->ReadInt32();
703 _G(views)[view].loops[loop].frames[frame].pic = in->ReadInt32();
704 }
705 }
706 }
707 return err;
708 }
709
WriteDynamicSprites(Stream * out)710 HSaveError WriteDynamicSprites(Stream *out) {
711 const soff_t ref_pos = out->GetPosition();
712 out->WriteInt32(0); // number of dynamic sprites
713 out->WriteInt32(0); // top index
714 int count = 0;
715 int top_index = 1;
716 for (size_t i = 1; i < _GP(spriteset).GetSpriteSlotCount(); ++i) {
717 if (_GP(game).SpriteInfos[i].Flags & SPF_DYNAMICALLOC) {
718 count++;
719 top_index = i;
720 out->WriteInt32(i);
721 out->WriteInt32(_GP(game).SpriteInfos[i].Flags);
722 serialize_bitmap(_GP(spriteset)[i], out);
723 }
724 }
725 const soff_t end_pos = out->GetPosition();
726 out->Seek(ref_pos, kSeekBegin);
727 out->WriteInt32(count);
728 out->WriteInt32(top_index);
729 out->Seek(end_pos, kSeekBegin);
730 return HSaveError::None();
731 }
732
ReadDynamicSprites(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)733 HSaveError ReadDynamicSprites(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
734 HSaveError err;
735 const int spr_count = in->ReadInt32();
736 // ensure the sprite set is at least large enough
737 // to accomodate top dynamic sprite index
738 const int top_index = in->ReadInt32();
739 _GP(spriteset).EnlargeTo(top_index);
740 for (int i = 0; i < spr_count; ++i) {
741 int id = in->ReadInt32();
742 int flags = in->ReadInt32();
743 add_dynamic_sprite(id, read_serialized_bitmap(in));
744 _GP(game).SpriteInfos[id].Flags = flags;
745 }
746 return err;
747 }
748
WriteOverlays(Stream * out)749 HSaveError WriteOverlays(Stream *out) {
750 out->WriteInt32(_G(numscreenover));
751 for (int i = 0; i < _G(numscreenover); ++i) {
752 _G(screenover)[i].WriteToFile(out);
753 serialize_bitmap(_G(screenover)[i].pic, out);
754 }
755 return HSaveError::None();
756 }
757
ReadOverlays(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)758 HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
759 HSaveError err;
760 int over_count = in->ReadInt32();
761 if (!AssertCompatLimit(err, over_count, MAX_SCREEN_OVERLAYS, "overlays"))
762 return err;
763 _G(numscreenover) = over_count;
764 for (int i = 0; i < _G(numscreenover); ++i) {
765 _G(screenover)[i].ReadFromFile(in, cmp_ver);
766 if (_G(screenover)[i].hasSerializedBitmap)
767 _G(screenover)[i].pic = read_serialized_bitmap(in);
768 }
769 return err;
770 }
771
WriteDynamicSurfaces(Stream * out)772 HSaveError WriteDynamicSurfaces(Stream *out) {
773 out->WriteInt32(MAX_DYNAMIC_SURFACES);
774 for (int i = 0; i < MAX_DYNAMIC_SURFACES; ++i) {
775 if (_G(dynamicallyCreatedSurfaces)[i] == nullptr) {
776 out->WriteInt8(0);
777 } else {
778 out->WriteInt8(1);
779 serialize_bitmap(_G(dynamicallyCreatedSurfaces)[i], out);
780 }
781 }
782 return HSaveError::None();
783 }
784
ReadDynamicSurfaces(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)785 HSaveError ReadDynamicSurfaces(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
786 HSaveError err;
787 if (!AssertCompatLimit(err, in->ReadInt32(), MAX_DYNAMIC_SURFACES, "Dynamic Surfaces"))
788 return err;
789 // Load the surfaces into a temporary array since ccUnserialiseObjects will destroy them otherwise
790 r_data.DynamicSurfaces.resize(MAX_DYNAMIC_SURFACES);
791 for (int i = 0; i < MAX_DYNAMIC_SURFACES; ++i) {
792 if (in->ReadInt8() == 0)
793 r_data.DynamicSurfaces[i] = nullptr;
794 else
795 r_data.DynamicSurfaces[i] = read_serialized_bitmap(in);
796 }
797 return err;
798 }
799
WriteScriptModules(Stream * out)800 HSaveError WriteScriptModules(Stream *out) {
801 // write the data segment of the global script
802 int data_len = _G(gameinst)->globaldatasize;
803 out->WriteInt32(data_len);
804 if (data_len > 0)
805 out->Write(_G(gameinst)->globaldata, data_len);
806 // write the script modules data segments
807 out->WriteInt32(_G(numScriptModules));
808 for (int i = 0; i < _G(numScriptModules); ++i) {
809 data_len = _GP(moduleInst)[i]->globaldatasize;
810 out->WriteInt32(data_len);
811 if (data_len > 0)
812 out->Write(_GP(moduleInst)[i]->globaldata, data_len);
813 }
814 return HSaveError::None();
815 }
816
ReadScriptModules(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)817 HSaveError ReadScriptModules(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
818 HSaveError err;
819 // read the global script data segment
820 int data_len = in->ReadInt32();
821 if (!AssertGameContent(err, data_len, pp.GlScDataSize, "global script data"))
822 return err;
823 r_data.GlobalScript.Len = data_len;
824 r_data.GlobalScript.Data.reset(new char[data_len]);
825 in->Read(r_data.GlobalScript.Data.get(), data_len);
826
827 if (!AssertGameContent(err, in->ReadInt32(), _G(numScriptModules), "Script Modules"))
828 return err;
829 r_data.ScriptModules.resize(_G(numScriptModules));
830 for (int i = 0; i < _G(numScriptModules); ++i) {
831 data_len = in->ReadInt32();
832 if (!AssertGameObjectContent(err, data_len, pp.ScMdDataSize[i], "script module data", "module", i))
833 return err;
834 r_data.ScriptModules[i].Len = data_len;
835 r_data.ScriptModules[i].Data.reset(new char[data_len]);
836 in->Read(r_data.ScriptModules[i].Data.get(), data_len);
837 }
838 return err;
839 }
840
WriteRoomStates(Stream * out)841 HSaveError WriteRoomStates(Stream *out) {
842 // write the room state for all the rooms the player has been in
843 out->WriteInt32(MAX_ROOMS);
844 for (int i = 0; i < MAX_ROOMS; ++i) {
845 if (isRoomStatusValid(i)) {
846 RoomStatus *roomstat = getRoomStatus(i);
847 if (roomstat->beenhere) {
848 out->WriteInt32(i);
849 WriteFormatTag(out, "RoomState", true);
850 roomstat->WriteToSavegame(out);
851 WriteFormatTag(out, "RoomState", false);
852 } else
853 out->WriteInt32(-1);
854 } else
855 out->WriteInt32(-1);
856 }
857 return HSaveError::None();
858 }
859
ReadRoomStates(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)860 HSaveError ReadRoomStates(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
861 HSaveError err;
862 int roomstat_count = in->ReadInt32();
863 for (; roomstat_count > 0; --roomstat_count) {
864 int id = in->ReadInt32();
865 // If id == -1, then the player has not been there yet (or room state was reset)
866 if (id != -1) {
867 if (!AssertCompatRange(err, id, 0, MAX_ROOMS - 1, "room index"))
868 return err;
869 if (!AssertFormatTagStrict(err, in, "RoomState", true))
870 return err;
871 RoomStatus *roomstat = getRoomStatus(id);
872 roomstat->ReadFromSavegame(in);
873 if (!AssertFormatTagStrict(err, in, "RoomState", false))
874 return err;
875 }
876 }
877 return HSaveError::None();
878 }
879
WriteThisRoom(Stream * out)880 HSaveError WriteThisRoom(Stream *out) {
881 out->WriteInt32(_G(displayed_room));
882 if (_G(displayed_room) < 0)
883 return HSaveError::None();
884
885 // modified room backgrounds
886 for (int i = 0; i < MAX_ROOM_BGFRAMES; ++i) {
887 out->WriteBool(_GP(play).raw_modified[i] != 0);
888 if (_GP(play).raw_modified[i])
889 serialize_bitmap(_GP(thisroom).BgFrames[i].Graphic.get(), out);
890 }
891 out->WriteBool(_G(raw_saved_screen) != nullptr);
892 if (_G(raw_saved_screen))
893 serialize_bitmap(_G(raw_saved_screen), out);
894
895 // room region state
896 for (int i = 0; i < MAX_ROOM_REGIONS; ++i) {
897 out->WriteInt32(_GP(thisroom).Regions[i].Light);
898 out->WriteInt32(_GP(thisroom).Regions[i].Tint);
899 }
900 for (int i = 0; i < MAX_WALK_AREAS + 1; ++i) {
901 out->WriteInt32(_GP(thisroom).WalkAreas[i].ScalingFar);
902 out->WriteInt32(_GP(thisroom).WalkAreas[i].ScalingNear);
903 }
904
905 // room object movement paths cache
906 out->WriteInt32(_GP(thisroom).ObjectCount + 1);
907 for (size_t i = 0; i < _GP(thisroom).ObjectCount + 1; ++i) {
908 _G(mls)[i].WriteToFile(out);
909 }
910
911 // room music volume
912 out->WriteInt32(_GP(thisroom).Options.MusicVolume);
913
914 // persistent room's indicator
915 const bool persist = _G(displayed_room) < MAX_ROOMS;
916 out->WriteBool(persist);
917 // write the current troom state, in case they save in temporary room
918 if (!persist)
919 _GP(troom).WriteToSavegame(out);
920 return HSaveError::None();
921 }
922
ReadThisRoom(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)923 HSaveError ReadThisRoom(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
924 HSaveError err;
925 _G(displayed_room) = in->ReadInt32();
926 if (_G(displayed_room) < 0)
927 return err;
928
929 // modified room backgrounds
930 for (int i = 0; i < MAX_ROOM_BGFRAMES; ++i) {
931 _GP(play).raw_modified[i] = in->ReadBool();
932 if (_GP(play).raw_modified[i])
933 r_data.RoomBkgScene[i].reset(read_serialized_bitmap(in));
934 else
935 r_data.RoomBkgScene[i] = nullptr;
936 }
937 if (in->ReadBool())
938 _G(raw_saved_screen) = read_serialized_bitmap(in);
939
940 // room region state
941 for (int i = 0; i < MAX_ROOM_REGIONS; ++i) {
942 r_data.RoomLightLevels[i] = in->ReadInt32();
943 r_data.RoomTintLevels[i] = in->ReadInt32();
944 }
945 for (int i = 0; i < MAX_WALK_AREAS + 1; ++i) {
946 r_data.RoomZoomLevels1[i] = in->ReadInt32();
947 r_data.RoomZoomLevels2[i] = in->ReadInt32();
948 }
949
950 // room object movement paths cache
951 int objmls_count = in->ReadInt32();
952 if (!AssertCompatLimit(err, objmls_count, CHMLSOFFS, "room object move lists"))
953 return err;
954 for (int i = 0; i < objmls_count; ++i) {
955 err = _G(mls)[i].ReadFromFile(in, cmp_ver > 0 ? 1 : 0);
956 if (!err)
957 return err;
958 }
959
960 // save the new room music vol for later use
961 r_data.RoomVolume = (RoomVolumeMod)in->ReadInt32();
962
963 // read the current troom state, in case they saved in temporary room
964 if (!in->ReadBool())
965 _GP(troom).ReadFromSavegame(in);
966
967 return HSaveError::None();
968 }
969
WriteManagedPool(Stream * out)970 HSaveError WriteManagedPool(Stream *out) {
971 ccSerializeAllObjects(out);
972 return HSaveError::None();
973 }
974
ReadManagedPool(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)975 HSaveError ReadManagedPool(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
976 if (ccUnserializeAllObjects(in, &_GP(ccUnserializer))) {
977 return new SavegameError(kSvgErr_GameObjectInitFailed,
978 String::FromFormat("Managed pool deserialization failed: %s", _G(ccErrorString).GetCStr()));
979 }
980 return HSaveError::None();
981 }
982
WritePluginData(Stream * out)983 HSaveError WritePluginData(Stream *out) {
984 auto pluginFileHandle = AGSE_SAVEGAME;
985 pl_set_file_handle(pluginFileHandle, out);
986 pl_run_plugin_hooks(AGSE_SAVEGAME, pluginFileHandle);
987 pl_clear_file_handle();
988 return HSaveError::None();
989 }
990
ReadPluginData(Stream * in,int32_t cmp_ver,const PreservedParams & pp,RestoredData & r_data)991 HSaveError ReadPluginData(Stream *in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) {
992 auto pluginFileHandle = AGSE_RESTOREGAME;
993 pl_set_file_handle(pluginFileHandle, in);
994 pl_run_plugin_hooks(AGSE_RESTOREGAME, pluginFileHandle);
995 pl_clear_file_handle();
996 return HSaveError::None();
997 }
998
999
1000 // Description of a supported game state serialization component
1001 struct ComponentHandler {
1002 String Name; // internal component's ID
1003 int32_t Version; // current version to write and the highest supported version
1004 int32_t LowestVersion; // lowest supported version that the engine can read
1005 HSaveError(*Serialize)(Stream *);
1006 HSaveError(*Unserialize)(Stream *, int32_t cmp_ver, const PreservedParams &, RestoredData &);
1007 };
1008
1009 // Array of supported components
1010 ComponentHandler ComponentHandlers[] = {
1011 {
1012 "Game State",
1013 kGSSvgVersion_350_10,
1014 kGSSvgVersion_Initial,
1015 WriteGameState,
1016 ReadGameState
1017 },
1018 {
1019 "Audio",
1020 1,
1021 0,
1022 WriteAudio,
1023 ReadAudio
1024 },
1025 {
1026 "Characters",
1027 1,
1028 0,
1029 WriteCharacters,
1030 ReadCharacters
1031 },
1032 {
1033 "Dialogs",
1034 0,
1035 0,
1036 WriteDialogs,
1037 ReadDialogs
1038 },
1039 {
1040 "GUI",
1041 kGuiSvgVersion_350,
1042 kGuiSvgVersion_Initial,
1043 WriteGUI,
1044 ReadGUI
1045 },
1046 {
1047 "Inventory Items",
1048 0,
1049 0,
1050 WriteInventory,
1051 ReadInventory
1052 },
1053 {
1054 "Mouse Cursors",
1055 0,
1056 0,
1057 WriteMouseCursors,
1058 ReadMouseCursors
1059 },
1060 {
1061 "Views",
1062 0,
1063 0,
1064 WriteViews,
1065 ReadViews
1066 },
1067 {
1068 "Dynamic Sprites",
1069 0,
1070 0,
1071 WriteDynamicSprites,
1072 ReadDynamicSprites
1073 },
1074 {
1075 "Overlays",
1076 1,
1077 0,
1078 WriteOverlays,
1079 ReadOverlays
1080 },
1081 {
1082 "Dynamic Surfaces",
1083 0,
1084 0,
1085 WriteDynamicSurfaces,
1086 ReadDynamicSurfaces
1087 },
1088 {
1089 "Script Modules",
1090 0,
1091 0,
1092 WriteScriptModules,
1093 ReadScriptModules
1094 },
1095 {
1096 "Room States",
1097 0,
1098 0,
1099 WriteRoomStates,
1100 ReadRoomStates
1101 },
1102 {
1103 "Loaded Room State",
1104 1,
1105 0,
1106 WriteThisRoom,
1107 ReadThisRoom
1108 },
1109 {
1110 "Managed Pool",
1111 0,
1112 0,
1113 WriteManagedPool,
1114 ReadManagedPool
1115 },
1116 {
1117 "Plugin Data",
1118 0,
1119 0,
1120 WritePluginData,
1121 ReadPluginData
1122 },
1123 { nullptr, 0, 0, nullptr, nullptr } // end of array
1124 };
1125
1126
1127 typedef std::map<String, ComponentHandler> HandlersMap;
GenerateHandlersMap(HandlersMap & map)1128 void GenerateHandlersMap(HandlersMap &map) {
1129 map.clear();
1130 for (int i = 0; !ComponentHandlers[i].Name.IsEmpty(); ++i)
1131 map[ComponentHandlers[i].Name] = ComponentHandlers[i];
1132 }
1133
1134 // A helper struct to pass to (de)serialization handlers
1135 struct SvgCmpReadHelper {
1136 SavegameVersion Version; // general savegame version
1137 const PreservedParams &PP; // previous game state kept for reference
1138 RestoredData &RData; // temporary storage for loaded data, that
1139 // will be applied after loading is done
1140 // The map of serialization handlers, one per supported component type ID
1141 HandlersMap Handlers;
1142
SvgCmpReadHelperAGS3::AGS::Engine::SavegameComponents::SvgCmpReadHelper1143 SvgCmpReadHelper(SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data)
1144 : Version(svg_version)
1145 , PP(pp)
1146 , RData(r_data) {
1147 }
1148 };
1149
1150 // The basic information about deserialized component, used for debugging purposes
1151 struct ComponentInfo {
1152 String Name; // internal component's ID
1153 int32_t Version; // data format version
1154 soff_t Offset; // offset at which an opening tag is located
1155 soff_t DataOffset; // offset at which component data begins
1156 soff_t DataSize; // expected size of component data
1157
ComponentInfoAGS3::AGS::Engine::SavegameComponents::ComponentInfo1158 ComponentInfo() : Version(-1), Offset(0), DataOffset(0), DataSize(0) {}
1159 };
1160
ReadComponent(Stream * in,SvgCmpReadHelper & hlp,ComponentInfo & info)1161 HSaveError ReadComponent(Stream *in, SvgCmpReadHelper &hlp, ComponentInfo &info) {
1162 info = ComponentInfo(); // reset in case of early error
1163 info.Offset = in->GetPosition();
1164 if (!ReadFormatTag(in, info.Name, true))
1165 return new SavegameError(kSvgErr_ComponentOpeningTagFormat);
1166 info.Version = in->ReadInt32();
1167 info.DataSize = hlp.Version >= kSvgVersion_Cmp_64bit ? in->ReadInt64() : in->ReadInt32();
1168 info.DataOffset = in->GetPosition();
1169
1170 // WORKAROUND: For some period, the component "Dynamic Surfaces"
1171 // was mis-named as "Drawing Surfaces"
1172 String componentName = info.Name;
1173 if (componentName == "Drawing Surfaces")
1174 componentName = "Dynamic Surfaces";
1175
1176 const ComponentHandler *handler = nullptr;
1177 std::map<String, ComponentHandler>::const_iterator it_hdr = hlp.Handlers.find(componentName);
1178 if (it_hdr != hlp.Handlers.end())
1179 handler = &it_hdr->_value;
1180
1181 // WORKAROUND: Managed Pool was incorrectly set as version 1
1182 // in the codebase originally imported to ScummVM
1183 if (componentName == "Managed Pool" && info.Version == 1
1184 && handler && handler->Version == 0)
1185 info.Version = 0;
1186
1187 if (!handler || !handler->Unserialize)
1188 return new SavegameError(kSvgErr_UnsupportedComponent);
1189 if (info.Version > handler->Version || info.Version < handler->LowestVersion)
1190 return new SavegameError(kSvgErr_UnsupportedComponentVersion, String::FromFormat("Saved version: %d, supported: %d - %d", info.Version, handler->LowestVersion, handler->Version));
1191 HSaveError err = handler->Unserialize(in, info.Version, hlp.PP, hlp.RData);
1192 if (!err)
1193 return err;
1194 if (in->GetPosition() - info.DataOffset != info.DataSize)
1195 return new SavegameError(kSvgErr_ComponentSizeMismatch, String::FromFormat("Expected: %lld, actual: %lld", info.DataSize, in->GetPosition() - info.DataOffset));
1196 if (!AssertFormatTag(in, info.Name, false))
1197 return new SavegameError(kSvgErr_ComponentClosingTagFormat);
1198 return HSaveError::None();
1199 }
1200
ReadAll(Stream * in,SavegameVersion svg_version,const PreservedParams & pp,RestoredData & r_data)1201 HSaveError ReadAll(Stream *in, SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data) {
1202 // Prepare a helper struct we will be passing to the block reading proc
1203 SvgCmpReadHelper hlp(svg_version, pp, r_data);
1204 GenerateHandlersMap(hlp.Handlers);
1205
1206 size_t idx = 0;
1207 if (!AssertFormatTag(in, ComponentListTag, true))
1208 return new SavegameError(kSvgErr_ComponentListOpeningTagFormat);
1209 do {
1210 // Look out for the end of the component list:
1211 // this is the only way how this function ends with success
1212 soff_t off = in->GetPosition();
1213 if (AssertFormatTag(in, ComponentListTag, false))
1214 return HSaveError::None();
1215 // If the list's end was not detected, then seek back and continue reading
1216 in->Seek(off, kSeekBegin);
1217
1218 ComponentInfo info;
1219 HSaveError err = ReadComponent(in, hlp, info);
1220 if (!err) {
1221 return new SavegameError(kSvgErr_ComponentUnserialization,
1222 String::FromFormat("(#%d) %s, version %i, at offset %lld.",
1223 idx, info.Name.IsEmpty() ? "unknown" : info.Name.GetCStr(), info.Version, info.Offset),
1224 err);
1225 }
1226 update_polled_stuff_if_runtime();
1227 idx++;
1228 } while (!in->EOS());
1229 return new SavegameError(kSvgErr_ComponentListClosingTagMissing);
1230 }
1231
WriteComponent(Stream * out,ComponentHandler & hdlr)1232 HSaveError WriteComponent(Stream *out, ComponentHandler &hdlr) {
1233 WriteFormatTag(out, hdlr.Name, true);
1234 out->WriteInt32(hdlr.Version);
1235 soff_t ref_pos = out->GetPosition();
1236 out->WriteInt64(0); // placeholder for the component size
1237 HSaveError err = hdlr.Serialize(out);
1238 soff_t end_pos = out->GetPosition();
1239 out->Seek(ref_pos, kSeekBegin);
1240 out->WriteInt64(end_pos - ref_pos - sizeof(int64_t)); // size of serialized component data
1241 out->Seek(end_pos, kSeekBegin);
1242 if (err)
1243 WriteFormatTag(out, hdlr.Name, false);
1244 return err;
1245 }
1246
WriteAllCommon(Stream * out)1247 HSaveError WriteAllCommon(Stream *out) {
1248 WriteFormatTag(out, ComponentListTag, true);
1249 for (int type = 0; !ComponentHandlers[type].Name.IsEmpty(); ++type) {
1250 HSaveError err = WriteComponent(out, ComponentHandlers[type]);
1251 if (!err) {
1252 return new SavegameError(kSvgErr_ComponentSerialization,
1253 String::FromFormat("Component: (#%d) %s", type, ComponentHandlers[type].Name.GetCStr()),
1254 err);
1255 }
1256 update_polled_stuff_if_runtime();
1257 }
1258 WriteFormatTag(out, ComponentListTag, false);
1259 return HSaveError::None();
1260 }
1261
1262 } // namespace SavegameBlocks
1263 } // namespace Engine
1264 } // namespace AGS
1265 } // namespace AGS3
1266