1 /*
2 * Portions of this file are copyright Rebirth contributors and licensed as
3 * described in COPYING.txt.
4 * Portions of this file are copyright Parallax Software and licensed
5 * according to the Parallax license below.
6 * See COPYING.txt for license details.
7
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
18 */
19
20 /*
21 *
22 * Functions to save/restore game state.
23 *
24 */
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <math.h>
29 #include <string.h>
30
31 #include "pstypes.h"
32 #include "inferno.h"
33 #include "segment.h"
34 #include "textures.h"
35 #include "wall.h"
36 #include "object.h"
37 #include "dxxerror.h"
38 #include "console.h"
39 #include "gamefont.h"
40 #include "gameseg.h"
41 #include "switch.h"
42 #include "game.h"
43 #include "newmenu.h"
44 #include "fuelcen.h"
45 #include "hash.h"
46 #include "piggy.h"
47 #include "player.h"
48 #include "playsave.h"
49 #include "cntrlcen.h"
50 #include "morph.h"
51 #include "weapon.h"
52 #include "render.h"
53 #include "gameseq.h"
54 #include "event.h"
55 #include "robot.h"
56 #include "newdemo.h"
57 #include "automap.h"
58 #include "piggy.h"
59 #include "text.h"
60 #include "mission.h"
61 #include "u_mem.h"
62 #include "args.h"
63 #include "ai.h"
64 #include "fireball.h"
65 #include "controls.h"
66 #include "hudmsg.h"
67 #include "state.h"
68 #include "multi.h"
69 #include "gr.h"
70 #if DXX_USE_OGL
71 #include "ogl_init.h"
72 #endif
73
74 #if DXX_USE_EDITOR
75 #include "editor/editor.h"
76 #endif
77
78 #include "compiler-range_for.h"
79 #include "d_levelstate.h"
80 #include "d_range.h"
81 #include "d_enumerate.h"
82 #include "partial_range.h"
83 #include "d_zip.h"
84 #include <utility>
85
86 #if defined(DXX_BUILD_DESCENT_I)
87 #define STATE_VERSION 7
88 #define STATE_MATCEN_VERSION 25 // specific version of metcen info written into D1 savegames. Currenlty equal to GAME_VERSION (see gamesave.cpp). If changed, then only along with STATE_VERSION.
89 #define STATE_COMPATIBLE_VERSION 6
90 #elif defined(DXX_BUILD_DESCENT_II)
91 #define STATE_VERSION 22
92 #define STATE_COMPATIBLE_VERSION 20
93 #endif
94 // 0 - Put DGSS (Descent Game State Save) id at tof.
95 // 1 - Added Difficulty level save
96 // 2 - Added cheats.enabled flag
97 // 3 - Added between levels save.
98 // 4 - Added mission support
99 // 5 - Mike changed ai and object structure.
100 // 6 - Added buggin' cheat save
101 // 7 - Added other cheat saves and game_id.
102 // 8 - Added AI stuff for escort and thief.
103 // 9 - Save palette with screen shot
104 // 12- Saved last_was_super array
105 // 13- Saved palette flash stuff
106 // 14- Save cloaking wall stuff
107 // 15- Save additional ai info
108 // 16- Save Light_subtracted
109 // 17- New marker save
110 // 18- Took out saving of old cheat status
111 // 19- Saved cheats.enabled flag
112 // 20- First_secret_visit
113 // 22- Omega_charge
114
115 #define THUMBNAIL_W 100
116 #define THUMBNAIL_H 50
117
118 constexpr char dgss_id[4] = {'D', 'G', 'S', 'S'};
119
120 unsigned state_game_id;
121
122 namespace dcx {
123
124 namespace {
125
126 constexpr unsigned NUM_SAVES = d_game_unique_state::MAXIMUM_SAVE_SLOTS.value;
127
128 struct relocated_player_data
129 {
130 fix shields;
131 int16_t num_robots_level;
132 int16_t num_robots_total;
133 uint16_t hostages_total;
134 uint8_t hostages_level;
135 };
136
137 struct savegame_mission_path {
138 std::array<char, 9> original;
139 std::array<char, DXX_MAX_MISSION_PATH_LENGTH> full;
140 };
141
142 enum class savegame_mission_name_abi : uint8_t
143 {
144 original,
145 pathname,
146 };
147
148 static_assert(sizeof(savegame_mission_path) == sizeof(savegame_mission_path::original) + sizeof(savegame_mission_path::full), "padding error");
149
150 }
151
152 }
153
154 namespace dsx {
155
156 namespace {
157
158 struct savegame_newmenu_items
159 {
160 struct error_no_saves_found
161 {
162 };
163 static constexpr unsigned decorative_item_count = 1;
164 using imenu_description_buffers_array = std::array<ntstring<NM_MAX_TEXT_LEN>, NUM_SAVES>;
165 imenu_description_buffers_array *const user_entered_savegame_descriptions;
166 d_game_unique_state::savegame_description *const caller_savegame_description;
167 d_game_unique_state::savegame_file_path &savegame_file_path;
168 enumerated_array<d_game_unique_state::savegame_file_path, NUM_SAVES, d_game_unique_state::save_slot> savegame_file_paths;
169 enumerated_array<d_game_unique_state::savegame_description, NUM_SAVES, d_game_unique_state::save_slot> savegame_descriptions;
170 enumerated_array<grs_bitmap_ptr, NUM_SAVES, d_game_unique_state::save_slot> sc_bmp;
171 std::array<newmenu_item, NUM_SAVES + decorative_item_count> m;
172 /* For saving a game, savegame_description is a
173 * caller-supplied buffer into which the user's text is placed, so
174 * that the caller can write that text into the file.
175 *
176 * For loading a game, savegame_description is nullptr.
177 */
178 savegame_newmenu_items(d_game_unique_state::savegame_description *savegame_description, d_game_unique_state::savegame_file_path &savegame_file_path, imenu_description_buffers_array *);
179 /* Test whether `selection` is an index that could be a valid
180 * choice. If `selection` can never be a valid choice, return
181 * false. If `selection` would be valid for a user who has every
182 * savegame slot in use, return true. This does not test whether
183 * there currently exists a savegame in the specified slot.
184 */
valid_savegame_indexdsx::__anondf2ed0ca0211::savegame_newmenu_items185 static unsigned valid_savegame_index(void *const user_entered_savegame_descriptions, const d_game_unique_state::save_slot selection)
186 {
187 return user_entered_savegame_descriptions
188 ? GameUniqueState.valid_save_slot(selection)
189 : GameUniqueState.valid_load_slot(selection);
190 }
valid_savegame_indexdsx::__anondf2ed0ca0211::savegame_newmenu_items191 unsigned valid_savegame_index(const d_game_unique_state::save_slot selection) const
192 {
193 return valid_savegame_index(user_entered_savegame_descriptions, selection);
194 }
get_count_valid_menuitem_entriesdsx::__anondf2ed0ca0211::savegame_newmenu_items195 std::size_t get_count_valid_menuitem_entries(d_game_unique_state::savegame_description *const savegame_description) const
196 {
197 return savegame_description ? m.size() - 1 : m.size();
198 }
199 };
200
201 struct savegame_chooser_newmenu : savegame_newmenu_items, newmenu
202 {
203 virtual window_event_result event_handler(const d_event &) override;
204 static std::unique_ptr<savegame_chooser_newmenu> create(menu_subtitle caption, grs_canvas &src, d_game_unique_state::savegame_description *const savegame_description, d_game_unique_state::savegame_file_path &savegame_file_path);
205 private:
206 void draw_handler(grs_canvas &canvas, const grs_bitmap &bmp);
207 void draw_handler();
208 savegame_chooser_newmenu(menu_subtitle caption, grs_canvas &src, d_game_unique_state::savegame_description *, d_game_unique_state::savegame_file_path &, imenu_description_buffers_array *);
build_save_slot_from_citemdsx::__anondf2ed0ca0211::savegame_chooser_newmenu209 d_game_unique_state::save_slot build_save_slot_from_citem() const
210 {
211 if (citem < decorative_item_count)
212 return d_game_unique_state::save_slot::None;
213 return static_cast<d_game_unique_state::save_slot>(citem - decorative_item_count);
214 }
215 };
216
create(menu_subtitle caption,grs_canvas & src,d_game_unique_state::savegame_description * const savegame_description,d_game_unique_state::savegame_file_path & savegame_file_path)217 std::unique_ptr<savegame_chooser_newmenu> savegame_chooser_newmenu::create(menu_subtitle caption, grs_canvas &src, d_game_unique_state::savegame_description *const savegame_description, d_game_unique_state::savegame_file_path &savegame_file_path)
218 {
219 std::unique_ptr<uint8_t[]> p(savegame_description
220 /* request to create a "Save game" menu */
221 ? reinterpret_cast<uint8_t *>(new typename std::aligned_union<sizeof(savegame_chooser_newmenu) + sizeof(savegame_chooser_newmenu::imenu_description_buffers_array), savegame_chooser_newmenu>::type)
222 /* request to create a "Load game" menu */
223 : reinterpret_cast<uint8_t *>(new typename std::aligned_union<0 /* no extra storage needed, and aligned_union will ensure the value is sufficient for the aligned type */, savegame_chooser_newmenu>::type)
224 );
225 new (p.get()) savegame_chooser_newmenu(caption, src, savegame_description, savegame_file_path, savegame_description ? reinterpret_cast<imenu_description_buffers_array *>(p.get() + sizeof(savegame_chooser_newmenu)) : nullptr);
226 return std::unique_ptr<savegame_chooser_newmenu>(reinterpret_cast<savegame_chooser_newmenu *>(p.release()));
227 }
228
savegame_chooser_newmenu(menu_subtitle subtitle,grs_canvas & src,d_game_unique_state::savegame_description * const savegame_description,d_game_unique_state::savegame_file_path & savegame_file_path,imenu_description_buffers_array * const user_entered_savegame_descriptions)229 savegame_chooser_newmenu::savegame_chooser_newmenu(menu_subtitle subtitle, grs_canvas &src, d_game_unique_state::savegame_description *const savegame_description, d_game_unique_state::savegame_file_path &savegame_file_path, imenu_description_buffers_array *const user_entered_savegame_descriptions) :
230 savegame_newmenu_items(savegame_description, savegame_file_path, user_entered_savegame_descriptions),
231 newmenu(menu_title{nullptr}, subtitle, menu_filename{nullptr}, tiny_mode_flag::normal, tab_processing_flag::ignore, adjusted_citem::create(unchecked_partial_range(m, get_count_valid_menuitem_entries(savegame_description)), decorative_item_count), src)
232 {
233 }
234
draw_handler(grs_canvas & canvas,const grs_bitmap & bmp)235 void savegame_chooser_newmenu::draw_handler(grs_canvas &canvas, const grs_bitmap &bmp)
236 {
237 const auto &&fspacx = FSPACX();
238 const auto &&fspacy = FSPACY();
239 #if DXX_USE_OGL
240 auto temp_canv = gr_create_canvas(THUMBNAIL_W * 2, THUMBNAIL_H * 24 / 10);
241 #else
242 auto temp_canv = gr_create_canvas(fspacx(THUMBNAIL_W), fspacy(THUMBNAIL_H));
243 #endif
244 const std::array<grs_point, 3> vertbuf{{
245 {0, 0},
246 {0, 0},
247 {i2f(THUMBNAIL_W * 2), i2f(THUMBNAIL_H * 24 / 10)}
248 }};
249 scale_bitmap(bmp, vertbuf, 0, temp_canv->cv_bitmap);
250 const auto bx = (canvas.cv_bitmap.bm_w / 2) - fspacx(THUMBNAIL_W / 2);
251 #if DXX_USE_OGL
252 ogl_ubitmapm_cs(canvas, bx, m[0].y - fspacy(3), fspacx(THUMBNAIL_W), fspacy(THUMBNAIL_H), temp_canv->cv_bitmap, ogl_colors::white);
253 #else
254 gr_bitmap(canvas, bx, m[0].y - 3, temp_canv->cv_bitmap);
255 #endif
256 }
257
draw_handler()258 void savegame_chooser_newmenu::draw_handler()
259 {
260 const auto choice = build_save_slot_from_citem();
261 if (!sc_bmp.valid_index(choice))
262 return;
263 if (auto &bmp = sc_bmp[choice])
264 draw_handler(w_canv, *bmp);
265 }
266
event_handler(const d_event & event)267 window_event_result savegame_chooser_newmenu::event_handler(const d_event &event)
268 {
269 switch (event.type)
270 {
271 case EVENT_NEWMENU_DRAW:
272 draw_handler();
273 return window_event_result::handled;
274 case EVENT_NEWMENU_SELECTED:
275 {
276 const auto citem = static_cast<const d_select_event &>(event).citem;
277 const auto choice = static_cast<d_game_unique_state::save_slot>(citem - decorative_item_count);
278 if (!valid_savegame_index(choice))
279 return window_event_result::close;
280 GameUniqueState.quicksave_selection = choice;
281 savegame_file_path = savegame_file_paths[choice];
282 if (const auto desc = caller_savegame_description)
283 {
284 auto &d = savegame_descriptions[choice];
285 if (d.front())
286 *desc = d;
287 else
288 {
289 const time_t t = time(nullptr);
290 if (const struct tm *ptm = (t == -1) ? nullptr : localtime(&t))
291 strftime(desc->data(), desc->size(), "%m-%d %H:%M:%S", ptm);
292 else
293 strcpy(desc->data(), "-no title-");
294 }
295 }
296 }
297 return window_event_result::close;
298 default:
299 break;
300 }
301 return newmenu::event_handler(event);
302 }
303
304 // Following functions convert object to object_rw and back to be written to/read from Savegames. Mostly object differs to object_rw in terms of timer values (fix/fix64). as we reset GameTime64 for writing so it can fit into fix it's not necessary to increment savegame version. But if we once store something else into object which might be useful after restoring, it might be handy to increment Savegame version and actually store these new infos.
305 // turn object to object_rw to be saved to Savegame.
306
state_object_to_object_rw(const object & obj,object_rw * const obj_rw)307 static void state_object_to_object_rw(const object &obj, object_rw *const obj_rw)
308 {
309 const auto otype = obj.type;
310 obj_rw->type = otype;
311 obj_rw->signature = static_cast<uint16_t>(obj.signature);
312 obj_rw->id = obj.id;
313 obj_rw->next = obj.next;
314 obj_rw->prev = obj.prev;
315 obj_rw->control_source = static_cast<uint8_t>(obj.control_source);
316 obj_rw->movement_source = static_cast<uint8_t>(obj.movement_source);
317 obj_rw->render_type = obj.render_type;
318 obj_rw->flags = obj.flags;
319 obj_rw->segnum = obj.segnum;
320 obj_rw->attached_obj = obj.attached_obj;
321 obj_rw->pos.x = obj.pos.x;
322 obj_rw->pos.y = obj.pos.y;
323 obj_rw->pos.z = obj.pos.z;
324 obj_rw->orient.rvec.x = obj.orient.rvec.x;
325 obj_rw->orient.rvec.y = obj.orient.rvec.y;
326 obj_rw->orient.rvec.z = obj.orient.rvec.z;
327 obj_rw->orient.fvec.x = obj.orient.fvec.x;
328 obj_rw->orient.fvec.y = obj.orient.fvec.y;
329 obj_rw->orient.fvec.z = obj.orient.fvec.z;
330 obj_rw->orient.uvec.x = obj.orient.uvec.x;
331 obj_rw->orient.uvec.y = obj.orient.uvec.y;
332 obj_rw->orient.uvec.z = obj.orient.uvec.z;
333 obj_rw->size = obj.size;
334 obj_rw->shields = obj.shields;
335 obj_rw->last_pos = obj.pos;
336 obj_rw->contains_type = obj.contains_type;
337 obj_rw->contains_id = obj.contains_id;
338 obj_rw->contains_count= obj.contains_count;
339 obj_rw->matcen_creator= obj.matcen_creator;
340 obj_rw->lifeleft = obj.lifeleft;
341
342 switch (typename object::movement_type{obj_rw->movement_source})
343 {
344 case object::movement_type::None:
345 obj_rw->mtype = {};
346 break;
347 case object::movement_type::physics:
348 obj_rw->mtype.phys_info.velocity.x = obj.mtype.phys_info.velocity.x;
349 obj_rw->mtype.phys_info.velocity.y = obj.mtype.phys_info.velocity.y;
350 obj_rw->mtype.phys_info.velocity.z = obj.mtype.phys_info.velocity.z;
351 obj_rw->mtype.phys_info.thrust.x = obj.mtype.phys_info.thrust.x;
352 obj_rw->mtype.phys_info.thrust.y = obj.mtype.phys_info.thrust.y;
353 obj_rw->mtype.phys_info.thrust.z = obj.mtype.phys_info.thrust.z;
354 obj_rw->mtype.phys_info.mass = obj.mtype.phys_info.mass;
355 obj_rw->mtype.phys_info.drag = obj.mtype.phys_info.drag;
356 obj_rw->mtype.phys_info.obsolete_brakes = 0;
357 obj_rw->mtype.phys_info.rotvel.x = obj.mtype.phys_info.rotvel.x;
358 obj_rw->mtype.phys_info.rotvel.y = obj.mtype.phys_info.rotvel.y;
359 obj_rw->mtype.phys_info.rotvel.z = obj.mtype.phys_info.rotvel.z;
360 obj_rw->mtype.phys_info.rotthrust.x = obj.mtype.phys_info.rotthrust.x;
361 obj_rw->mtype.phys_info.rotthrust.y = obj.mtype.phys_info.rotthrust.y;
362 obj_rw->mtype.phys_info.rotthrust.z = obj.mtype.phys_info.rotthrust.z;
363 obj_rw->mtype.phys_info.turnroll = obj.mtype.phys_info.turnroll;
364 obj_rw->mtype.phys_info.flags = obj.mtype.phys_info.flags;
365 break;
366
367 case object::movement_type::spinning:
368 obj_rw->mtype.spin_rate.x = obj.mtype.spin_rate.x;
369 obj_rw->mtype.spin_rate.y = obj.mtype.spin_rate.y;
370 obj_rw->mtype.spin_rate.z = obj.mtype.spin_rate.z;
371 break;
372 }
373
374 switch (typename object::control_type{obj_rw->control_source})
375 {
376 case object::control_type::weapon:
377 obj_rw->ctype.laser_info.parent_type = obj.ctype.laser_info.parent_type;
378 obj_rw->ctype.laser_info.parent_num = obj.ctype.laser_info.parent_num;
379 obj_rw->ctype.laser_info.parent_signature = static_cast<uint16_t>(obj.ctype.laser_info.parent_signature);
380 if (obj.ctype.laser_info.creation_time - GameTime64 < F1_0*(-18000))
381 obj_rw->ctype.laser_info.creation_time = F1_0*(-18000);
382 else
383 obj_rw->ctype.laser_info.creation_time = obj.ctype.laser_info.creation_time - GameTime64;
384 obj_rw->ctype.laser_info.last_hitobj = obj.ctype.laser_info.get_last_hitobj();
385 obj_rw->ctype.laser_info.track_goal = obj.ctype.laser_info.track_goal;
386 obj_rw->ctype.laser_info.multiplier = obj.ctype.laser_info.multiplier;
387 break;
388
389 case object::control_type::explosion:
390 obj_rw->ctype.expl_info.spawn_time = obj.ctype.expl_info.spawn_time;
391 obj_rw->ctype.expl_info.delete_time = obj.ctype.expl_info.delete_time;
392 obj_rw->ctype.expl_info.delete_objnum = obj.ctype.expl_info.delete_objnum;
393 obj_rw->ctype.expl_info.attach_parent = obj.ctype.expl_info.attach_parent;
394 obj_rw->ctype.expl_info.prev_attach = obj.ctype.expl_info.prev_attach;
395 obj_rw->ctype.expl_info.next_attach = obj.ctype.expl_info.next_attach;
396 break;
397
398 case object::control_type::ai:
399 {
400 int i;
401 obj_rw->ctype.ai_info.behavior = static_cast<uint8_t>(obj.ctype.ai_info.behavior);
402 for (i = 0; i < MAX_AI_FLAGS; i++)
403 obj_rw->ctype.ai_info.flags[i] = obj.ctype.ai_info.flags[i];
404 obj_rw->ctype.ai_info.hide_segment = obj.ctype.ai_info.hide_segment;
405 obj_rw->ctype.ai_info.hide_index = obj.ctype.ai_info.hide_index;
406 obj_rw->ctype.ai_info.path_length = obj.ctype.ai_info.path_length;
407 obj_rw->ctype.ai_info.cur_path_index = obj.ctype.ai_info.cur_path_index;
408 obj_rw->ctype.ai_info.danger_laser_num = obj.ctype.ai_info.danger_laser_num;
409 if (obj.ctype.ai_info.danger_laser_num != object_none)
410 obj_rw->ctype.ai_info.danger_laser_signature = static_cast<uint16_t>(obj.ctype.ai_info.danger_laser_signature);
411 else
412 obj_rw->ctype.ai_info.danger_laser_signature = 0;
413 #if defined(DXX_BUILD_DESCENT_I)
414 obj_rw->ctype.ai_info.follow_path_start_seg = segment_none;
415 obj_rw->ctype.ai_info.follow_path_end_seg = segment_none;
416 #elif defined(DXX_BUILD_DESCENT_II)
417 obj_rw->ctype.ai_info.dying_sound_playing = obj.ctype.ai_info.dying_sound_playing;
418 if (obj.ctype.ai_info.dying_start_time == 0) // if bot not dead, anything but 0 will kill it
419 obj_rw->ctype.ai_info.dying_start_time = 0;
420 else
421 obj_rw->ctype.ai_info.dying_start_time = obj.ctype.ai_info.dying_start_time - GameTime64;
422 #endif
423 break;
424 }
425
426 case object::control_type::light:
427 obj_rw->ctype.light_info.intensity = obj.ctype.light_info.intensity;
428 break;
429
430 case object::control_type::powerup:
431 obj_rw->ctype.powerup_info.count = obj.ctype.powerup_info.count;
432 #if defined(DXX_BUILD_DESCENT_II)
433 if (obj.ctype.powerup_info.creation_time - GameTime64 < F1_0*(-18000))
434 obj_rw->ctype.powerup_info.creation_time = F1_0*(-18000);
435 else
436 obj_rw->ctype.powerup_info.creation_time = obj.ctype.powerup_info.creation_time - GameTime64;
437 obj_rw->ctype.powerup_info.flags = obj.ctype.powerup_info.flags;
438 #endif
439 break;
440 case object::control_type::None:
441 case object::control_type::flying:
442 case object::control_type::slew:
443 case object::control_type::flythrough:
444 case object::control_type::repaircen:
445 case object::control_type::morph:
446 case object::control_type::debris:
447 case object::control_type::remote:
448 default:
449 break;
450 }
451
452 switch (obj_rw->render_type)
453 {
454 case RT_MORPH:
455 case RT_POLYOBJ:
456 case RT_NONE: // HACK below
457 {
458 int i;
459 if (obj.render_type == RT_NONE && obj.type != OBJ_GHOST) // HACK: when a player is dead or not connected yet, clients still expect to get polyobj data - even if render_type == RT_NONE at this time. Here it's not important, but it might be for Multiplayer Savegames.
460 break;
461 obj_rw->rtype.pobj_info.model_num = obj.rtype.pobj_info.model_num;
462 for (i=0;i<MAX_SUBMODELS;i++)
463 {
464 obj_rw->rtype.pobj_info.anim_angles[i].p = obj.rtype.pobj_info.anim_angles[i].p;
465 obj_rw->rtype.pobj_info.anim_angles[i].b = obj.rtype.pobj_info.anim_angles[i].b;
466 obj_rw->rtype.pobj_info.anim_angles[i].h = obj.rtype.pobj_info.anim_angles[i].h;
467 }
468 obj_rw->rtype.pobj_info.subobj_flags = obj.rtype.pobj_info.subobj_flags;
469 obj_rw->rtype.pobj_info.tmap_override = obj.rtype.pobj_info.tmap_override;
470 obj_rw->rtype.pobj_info.alt_textures = obj.rtype.pobj_info.alt_textures;
471 break;
472 }
473
474 case RT_WEAPON_VCLIP:
475 case RT_HOSTAGE:
476 case RT_POWERUP:
477 case RT_FIREBALL:
478 obj_rw->rtype.vclip_info.vclip_num = obj.rtype.vclip_info.vclip_num;
479 obj_rw->rtype.vclip_info.frametime = obj.rtype.vclip_info.frametime;
480 obj_rw->rtype.vclip_info.framenum = obj.rtype.vclip_info.framenum;
481 break;
482
483 case RT_LASER:
484 break;
485
486 }
487 }
488
489 // turn object_rw to object after reading from Savegame
state_object_rw_to_object(const object_rw * const obj_rw,object & obj)490 static void state_object_rw_to_object(const object_rw *const obj_rw, object &obj)
491 {
492 obj = {};
493 DXX_POISON_VAR(obj, 0xfd);
494 set_object_type(obj, obj_rw->type);
495 if (obj.type == OBJ_NONE)
496 {
497 obj.signature = object_signature_t{0};
498 return;
499 }
500
501 obj.signature = object_signature_t{static_cast<uint16_t>(obj_rw->signature)};
502 obj.id = obj_rw->id;
503 obj.next = obj_rw->next;
504 obj.prev = obj_rw->prev;
505 obj.control_source = typename object::control_type{obj_rw->control_source};
506 obj.movement_source = typename object::movement_type{obj_rw->movement_source};
507 const auto render_type = obj_rw->render_type;
508 if (valid_render_type(render_type))
509 obj.render_type = render_type_t{render_type};
510 else
511 {
512 con_printf(CON_URGENT, "save file used bogus render type %#x for object %p; using none instead", render_type, &obj);
513 obj.render_type = RT_NONE;
514 }
515 obj.flags = obj_rw->flags;
516 obj.segnum = obj_rw->segnum;
517 obj.attached_obj = obj_rw->attached_obj;
518 obj.pos.x = obj_rw->pos.x;
519 obj.pos.y = obj_rw->pos.y;
520 obj.pos.z = obj_rw->pos.z;
521 obj.orient.rvec.x = obj_rw->orient.rvec.x;
522 obj.orient.rvec.y = obj_rw->orient.rvec.y;
523 obj.orient.rvec.z = obj_rw->orient.rvec.z;
524 obj.orient.fvec.x = obj_rw->orient.fvec.x;
525 obj.orient.fvec.y = obj_rw->orient.fvec.y;
526 obj.orient.fvec.z = obj_rw->orient.fvec.z;
527 obj.orient.uvec.x = obj_rw->orient.uvec.x;
528 obj.orient.uvec.y = obj_rw->orient.uvec.y;
529 obj.orient.uvec.z = obj_rw->orient.uvec.z;
530 obj.size = obj_rw->size;
531 obj.shields = obj_rw->shields;
532 obj.contains_type = obj_rw->contains_type;
533 obj.contains_id = obj_rw->contains_id;
534 obj.contains_count= obj_rw->contains_count;
535 obj.matcen_creator= obj_rw->matcen_creator;
536 obj.lifeleft = obj_rw->lifeleft;
537
538 switch (obj.movement_source)
539 {
540 case object::movement_type::None:
541 break;
542 case object::movement_type::physics:
543 obj.mtype.phys_info.velocity.x = obj_rw->mtype.phys_info.velocity.x;
544 obj.mtype.phys_info.velocity.y = obj_rw->mtype.phys_info.velocity.y;
545 obj.mtype.phys_info.velocity.z = obj_rw->mtype.phys_info.velocity.z;
546 obj.mtype.phys_info.thrust.x = obj_rw->mtype.phys_info.thrust.x;
547 obj.mtype.phys_info.thrust.y = obj_rw->mtype.phys_info.thrust.y;
548 obj.mtype.phys_info.thrust.z = obj_rw->mtype.phys_info.thrust.z;
549 obj.mtype.phys_info.mass = obj_rw->mtype.phys_info.mass;
550 obj.mtype.phys_info.drag = obj_rw->mtype.phys_info.drag;
551 obj.mtype.phys_info.rotvel.x = obj_rw->mtype.phys_info.rotvel.x;
552 obj.mtype.phys_info.rotvel.y = obj_rw->mtype.phys_info.rotvel.y;
553 obj.mtype.phys_info.rotvel.z = obj_rw->mtype.phys_info.rotvel.z;
554 obj.mtype.phys_info.rotthrust.x = obj_rw->mtype.phys_info.rotthrust.x;
555 obj.mtype.phys_info.rotthrust.y = obj_rw->mtype.phys_info.rotthrust.y;
556 obj.mtype.phys_info.rotthrust.z = obj_rw->mtype.phys_info.rotthrust.z;
557 obj.mtype.phys_info.turnroll = obj_rw->mtype.phys_info.turnroll;
558 obj.mtype.phys_info.flags = obj_rw->mtype.phys_info.flags;
559 break;
560
561 case object::movement_type::spinning:
562 obj.mtype.spin_rate.x = obj_rw->mtype.spin_rate.x;
563 obj.mtype.spin_rate.y = obj_rw->mtype.spin_rate.y;
564 obj.mtype.spin_rate.z = obj_rw->mtype.spin_rate.z;
565 break;
566 }
567
568 switch (obj.control_source)
569 {
570 case object::control_type::weapon:
571 obj.ctype.laser_info.parent_type = obj_rw->ctype.laser_info.parent_type;
572 obj.ctype.laser_info.parent_num = obj_rw->ctype.laser_info.parent_num;
573 obj.ctype.laser_info.parent_signature = object_signature_t{static_cast<uint16_t>(obj_rw->ctype.laser_info.parent_signature)};
574 obj.ctype.laser_info.creation_time = obj_rw->ctype.laser_info.creation_time;
575 obj.ctype.laser_info.reset_hitobj(obj_rw->ctype.laser_info.last_hitobj);
576 obj.ctype.laser_info.track_goal = obj_rw->ctype.laser_info.track_goal;
577 obj.ctype.laser_info.multiplier = obj_rw->ctype.laser_info.multiplier;
578 #if defined(DXX_BUILD_DESCENT_II)
579 obj.ctype.laser_info.last_afterburner_time = 0;
580 #endif
581 break;
582
583 case object::control_type::explosion:
584 obj.ctype.expl_info.spawn_time = obj_rw->ctype.expl_info.spawn_time;
585 obj.ctype.expl_info.delete_time = obj_rw->ctype.expl_info.delete_time;
586 obj.ctype.expl_info.delete_objnum = obj_rw->ctype.expl_info.delete_objnum;
587 obj.ctype.expl_info.attach_parent = obj_rw->ctype.expl_info.attach_parent;
588 obj.ctype.expl_info.prev_attach = obj_rw->ctype.expl_info.prev_attach;
589 obj.ctype.expl_info.next_attach = obj_rw->ctype.expl_info.next_attach;
590 break;
591
592 case object::control_type::ai:
593 {
594 int i;
595 obj.ctype.ai_info.behavior = static_cast<ai_behavior>(obj_rw->ctype.ai_info.behavior);
596 for (i = 0; i < MAX_AI_FLAGS; i++)
597 obj.ctype.ai_info.flags[i] = obj_rw->ctype.ai_info.flags[i];
598 obj.ctype.ai_info.hide_segment = obj_rw->ctype.ai_info.hide_segment;
599 obj.ctype.ai_info.hide_index = obj_rw->ctype.ai_info.hide_index;
600 obj.ctype.ai_info.path_length = obj_rw->ctype.ai_info.path_length;
601 obj.ctype.ai_info.cur_path_index = obj_rw->ctype.ai_info.cur_path_index;
602 obj.ctype.ai_info.danger_laser_num = obj_rw->ctype.ai_info.danger_laser_num;
603 if (obj.ctype.ai_info.danger_laser_num != object_none)
604 obj.ctype.ai_info.danger_laser_signature = object_signature_t{static_cast<uint16_t>(obj_rw->ctype.ai_info.danger_laser_signature)};
605 #if defined(DXX_BUILD_DESCENT_I)
606 #elif defined(DXX_BUILD_DESCENT_II)
607 obj.ctype.ai_info.dying_sound_playing = obj_rw->ctype.ai_info.dying_sound_playing;
608 obj.ctype.ai_info.dying_start_time = obj_rw->ctype.ai_info.dying_start_time;
609 #endif
610 break;
611 }
612
613 case object::control_type::light:
614 obj.ctype.light_info.intensity = obj_rw->ctype.light_info.intensity;
615 break;
616
617 case object::control_type::powerup:
618 obj.ctype.powerup_info.count = obj_rw->ctype.powerup_info.count;
619 #if defined(DXX_BUILD_DESCENT_I)
620 obj.ctype.powerup_info.creation_time = 0;
621 obj.ctype.powerup_info.flags = 0;
622 #elif defined(DXX_BUILD_DESCENT_II)
623 obj.ctype.powerup_info.creation_time = obj_rw->ctype.powerup_info.creation_time;
624 obj.ctype.powerup_info.flags = obj_rw->ctype.powerup_info.flags;
625 #endif
626 break;
627 case object::control_type::cntrlcen:
628 {
629 if (obj.type == OBJ_GHOST)
630 {
631 /* Boss missions convert the reactor into OBJ_GHOST
632 * instead of freeing it. Old releases (before
633 * ed46a05296f9d480f934d8c951c4755ebac1d5e7 ("Update
634 * control_type when ghosting reactor")) did not update
635 * `control_type`, so games saved by those releases have an
636 * object with obj->type == OBJ_GHOST and obj->control_source ==
637 * object::control_type::cntrlcen. That inconsistency triggers an assertion down
638 * in `calc_controlcen_gun_point` because obj->type !=
639 * OBJ_CNTRLCEN.
640 *
641 * Add a special case here to correct this
642 * inconsistency.
643 */
644 obj.control_source = object::control_type::None;
645 break;
646 }
647 // gun points of reactor now part of the object but of course not saved in object_rw and overwritten due to reset_objects(). Let's just recompute them.
648 calc_controlcen_gun_point(obj);
649 break;
650 }
651 case object::control_type::None:
652 case object::control_type::flying:
653 case object::control_type::slew:
654 case object::control_type::flythrough:
655 case object::control_type::repaircen:
656 case object::control_type::morph:
657 case object::control_type::debris:
658 case object::control_type::remote:
659 default:
660 break;
661 }
662
663 switch (obj.render_type)
664 {
665 case RT_MORPH:
666 case RT_POLYOBJ:
667 case RT_NONE: // HACK below
668 {
669 int i;
670 if (obj.render_type == RT_NONE && obj.type != OBJ_GHOST) // HACK: when a player is dead or not connected yet, clients still expect to get polyobj data - even if render_type == RT_NONE at this time. Here it's not important, but it might be for Multiplayer Savegames.
671 break;
672 obj.rtype.pobj_info.model_num = obj_rw->rtype.pobj_info.model_num;
673 for (i=0;i<MAX_SUBMODELS;i++)
674 {
675 obj.rtype.pobj_info.anim_angles[i].p = obj_rw->rtype.pobj_info.anim_angles[i].p;
676 obj.rtype.pobj_info.anim_angles[i].b = obj_rw->rtype.pobj_info.anim_angles[i].b;
677 obj.rtype.pobj_info.anim_angles[i].h = obj_rw->rtype.pobj_info.anim_angles[i].h;
678 }
679 obj.rtype.pobj_info.subobj_flags = obj_rw->rtype.pobj_info.subobj_flags;
680 obj.rtype.pobj_info.tmap_override = obj_rw->rtype.pobj_info.tmap_override;
681 obj.rtype.pobj_info.alt_textures = obj_rw->rtype.pobj_info.alt_textures;
682 break;
683 }
684
685 case RT_WEAPON_VCLIP:
686 case RT_HOSTAGE:
687 case RT_POWERUP:
688 case RT_FIREBALL:
689 obj.rtype.vclip_info.vclip_num = obj_rw->rtype.vclip_info.vclip_num;
690 obj.rtype.vclip_info.frametime = obj_rw->rtype.vclip_info.frametime;
691 obj.rtype.vclip_info.framenum = obj_rw->rtype.vclip_info.framenum;
692 break;
693
694 case RT_LASER:
695 break;
696
697 }
698 }
699
700 }
701
deny_save_game(fvcobjptr & vcobjptr,const d_level_unique_control_center_state & LevelUniqueControlCenterState,const d_game_unique_state & GameUniqueState)702 deny_save_result deny_save_game(fvcobjptr &vcobjptr, const d_level_unique_control_center_state &LevelUniqueControlCenterState, const d_game_unique_state &GameUniqueState)
703 {
704 #if defined(DXX_BUILD_DESCENT_I)
705 (void)GameUniqueState;
706 #elif defined(DXX_BUILD_DESCENT_II)
707 if (Current_level_num < 0)
708 return deny_save_result::denied;
709 if (GameUniqueState.Final_boss_countdown_time) //don't allow save while final boss is dying
710 return deny_save_result::denied;
711 #endif
712 return deny_save_game(vcobjptr, LevelUniqueControlCenterState);
713 }
714
715 namespace {
716
717 // Following functions convert player to player_rw and back to be written to/read from Savegames. player only differ to player_rw in terms of timer values (fix/fix64). as we reset GameTime64 for writing so it can fit into fix it's not necessary to increment savegame version. But if we once store something else into object which might be useful after restoring, it might be handy to increment Savegame version and actually store these new infos.
718 // turn player to player_rw to be saved to Savegame.
state_player_to_player_rw(const relocated_player_data & rpd,const player * pl,player_rw * pl_rw,const player_info & pl_info)719 static void state_player_to_player_rw(const relocated_player_data &rpd, const player *pl, player_rw *pl_rw, const player_info &pl_info)
720 {
721 int i=0;
722 pl_rw->callsign = pl->callsign;
723 memset(pl_rw->net_address, 0, 6);
724 pl_rw->connected = pl->connected;
725 pl_rw->objnum = pl->objnum;
726 pl_rw->n_packets_got = 0;
727 pl_rw->n_packets_sent = 0;
728 pl_rw->flags = pl_info.powerup_flags.get_player_flags();
729 pl_rw->energy = pl_info.energy;
730 pl_rw->shields = rpd.shields;
731 /*
732 * The savegame only allocates a uint8_t for this value. If the
733 * player has exceeded the maximum representable value, cap at that
734 * value. This is better than truncating the value, since the
735 * player will get to keep more lives this way than with truncation.
736 */
737 pl_rw->lives = std::min<unsigned>(pl->lives, std::numeric_limits<uint8_t>::max());
738 pl_rw->level = pl->level;
739 pl_rw->laser_level = static_cast<uint8_t>(pl_info.laser_level);
740 pl_rw->starting_level = pl->starting_level;
741 pl_rw->killer_objnum = pl_info.killer_objnum;
742 pl_rw->primary_weapon_flags = pl_info.primary_weapon_flags;
743 #if defined(DXX_BUILD_DESCENT_I)
744 // make sure no side effects for Mac demo
745 pl_rw->secondary_weapon_flags = 0x0f | (pl_info.secondary_ammo[MEGA_INDEX] > 0) << MEGA_INDEX;
746 #elif defined(DXX_BUILD_DESCENT_II)
747 // make sure no side effects for PC demo
748 pl_rw->secondary_weapon_flags = 0xef | (pl_info.secondary_ammo[MEGA_INDEX] > 0) << MEGA_INDEX
749 | (pl_info.secondary_ammo[SMISSILE4_INDEX] > 0) << SMISSILE4_INDEX // mercury missile
750 | (pl_info.secondary_ammo[SMISSILE5_INDEX] > 0) << SMISSILE5_INDEX; // earthshaker missile
751 #endif
752 pl_rw->obsolete_primary_ammo = {};
753 pl_rw->vulcan_ammo = pl_info.vulcan_ammo;
754 for (i = 0; i < MAX_SECONDARY_WEAPONS; i++)
755 pl_rw->secondary_ammo[i] = pl_info.secondary_ammo[i];
756 #if defined(DXX_BUILD_DESCENT_II)
757 pl_rw->pad = 0;
758 #endif
759 pl_rw->last_score = pl_info.mission.last_score;
760 pl_rw->score = pl_info.mission.score;
761 pl_rw->time_level = pl->time_level;
762 pl_rw->time_total = pl->time_total;
763 if (!(pl_info.powerup_flags & PLAYER_FLAGS_CLOAKED) || pl_info.cloak_time - GameTime64 < F1_0*(-18000))
764 pl_rw->cloak_time = F1_0*(-18000);
765 else
766 pl_rw->cloak_time = pl_info.cloak_time - GameTime64;
767 if (!(pl_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE) || pl_info.invulnerable_time - GameTime64 < F1_0*(-18000))
768 pl_rw->invulnerable_time = F1_0*(-18000);
769 else
770 pl_rw->invulnerable_time = pl_info.invulnerable_time - GameTime64;
771 #if defined(DXX_BUILD_DESCENT_II)
772 pl_rw->KillGoalCount = pl_info.KillGoalCount;
773 #endif
774 pl_rw->net_killed_total = pl_info.net_killed_total;
775 pl_rw->net_kills_total = pl_info.net_kills_total;
776 pl_rw->num_kills_level = pl->num_kills_level;
777 pl_rw->num_kills_total = pl->num_kills_total;
778 pl_rw->num_robots_level = LevelUniqueObjectState.accumulated_robots;
779 pl_rw->num_robots_total = GameUniqueState.accumulated_robots;
780 pl_rw->hostages_rescued_total = pl_info.mission.hostages_rescued_total;
781 pl_rw->hostages_total = GameUniqueState.total_hostages;
782 pl_rw->hostages_on_board = pl_info.mission.hostages_on_board;
783 pl_rw->hostages_level = LevelUniqueObjectState.total_hostages;
784 pl_rw->homing_object_dist = pl_info.homing_object_dist;
785 pl_rw->hours_level = pl->hours_level;
786 pl_rw->hours_total = pl->hours_total;
787 }
788
789 // turn player_rw to player after reading from Savegame
790
state_player_rw_to_player(const player_rw * pl_rw,player * pl,player_info & pl_info,relocated_player_data & rpd)791 static void state_player_rw_to_player(const player_rw *pl_rw, player *pl, player_info &pl_info, relocated_player_data &rpd)
792 {
793 int i=0;
794 pl->callsign = pl_rw->callsign;
795 pl->connected = pl_rw->connected;
796 pl->objnum = pl_rw->objnum;
797 pl_info.powerup_flags = player_flags(pl_rw->flags);
798 pl_info.energy = pl_rw->energy;
799 rpd.shields = pl_rw->shields;
800 pl->lives = pl_rw->lives;
801 pl->level = pl_rw->level;
802 pl_info.laser_level = laser_level{pl_rw->laser_level};
803 pl->starting_level = pl_rw->starting_level;
804 pl_info.killer_objnum = pl_rw->killer_objnum;
805 pl_info.primary_weapon_flags = pl_rw->primary_weapon_flags;
806 pl_info.vulcan_ammo = pl_rw->vulcan_ammo;
807 for (i = 0; i < MAX_SECONDARY_WEAPONS; i++)
808 pl_info.secondary_ammo[i] = pl_rw->secondary_ammo[i];
809 pl_info.mission.last_score = pl_rw->last_score;
810 pl_info.mission.score = pl_rw->score;
811 pl->time_level = pl_rw->time_level;
812 pl->time_total = pl_rw->time_total;
813 pl_info.cloak_time = pl_rw->cloak_time;
814 pl_info.invulnerable_time = pl_rw->invulnerable_time;
815 #if defined(DXX_BUILD_DESCENT_I)
816 pl_info.KillGoalCount = 0;
817 #elif defined(DXX_BUILD_DESCENT_II)
818 pl_info.KillGoalCount = pl_rw->KillGoalCount;
819 #endif
820 pl_info.net_killed_total = pl_rw->net_killed_total;
821 pl_info.net_kills_total = pl_rw->net_kills_total;
822 pl->num_kills_level = pl_rw->num_kills_level;
823 pl->num_kills_total = pl_rw->num_kills_total;
824 rpd.num_robots_level = pl_rw->num_robots_level;
825 rpd.num_robots_total = pl_rw->num_robots_total;
826 pl_info.mission.hostages_rescued_total = pl_rw->hostages_rescued_total;
827 rpd.hostages_total = pl_rw->hostages_total;
828 pl_info.mission.hostages_on_board = pl_rw->hostages_on_board;
829 rpd.hostages_level = pl_rw->hostages_level;
830 pl_info.homing_object_dist = pl_rw->homing_object_dist;
831 pl->hours_level = pl_rw->hours_level;
832 pl->hours_total = pl_rw->hours_total;
833 }
834
state_write_player(PHYSFS_File * fp,const player & pl,const relocated_player_data & rpd,const player_info & pl_info)835 static void state_write_player(PHYSFS_File *fp, const player &pl, const relocated_player_data &rpd, const player_info &pl_info)
836 {
837 player_rw pl_rw;
838 state_player_to_player_rw(rpd, &pl, &pl_rw, pl_info);
839 PHYSFS_write(fp, &pl_rw, sizeof(pl_rw), 1);
840 }
841
state_read_player(PHYSFS_File * fp,player & pl,int swap,player_info & pl_info,relocated_player_data & rpd)842 static void state_read_player(PHYSFS_File *fp, player &pl, int swap, player_info &pl_info, relocated_player_data &rpd)
843 {
844 player_rw pl_rw;
845 PHYSFS_read(fp, &pl_rw, sizeof(pl_rw), 1);
846 player_rw_swap(&pl_rw, swap);
847 state_player_rw_to_player(&pl_rw, &pl, pl_info, rpd);
848 }
849
850 }
851
852 }
853
854 namespace dcx {
855
856 namespace {
857
state_format_savegame_filename(d_game_unique_state::savegame_file_path & filename,const unsigned i)858 void state_format_savegame_filename(d_game_unique_state::savegame_file_path &filename, const unsigned i)
859 {
860 snprintf(filename.data(), filename.size(), PLAYER_DIRECTORY_STRING("%.8s.%cg%x"), static_cast<const char *>(InterfaceUniqueState.PilotName), (Game_mode & GM_MULTI_COOP) ? 'm' : 's', i);
861 }
862
state_autosave_game(const int multiplayer)863 void state_autosave_game(const int multiplayer)
864 {
865 d_game_unique_state::savegame_description desc;
866 const char *p;
867 time_t t = time(nullptr);
868 if (struct tm *ptm = (t == -1) ? nullptr : localtime(&t))
869 {
870 p = desc.data();
871 strftime(desc.data(), desc.size(), "auto %m-%d %H:%M:%S", ptm);
872 }
873 else
874 p = "<autosave>";
875 if (multiplayer)
876 {
877 const auto &&player_range = partial_const_range(Players, N_players);
878 multi_execute_save_game(d_game_unique_state::save_slot::_autosave, desc, player_range);
879 }
880 else
881 {
882 d_game_unique_state::savegame_file_path filename;
883 state_format_savegame_filename(filename, NUM_SAVES - 1);
884 if (state_save_all_sub(filename.data(), p))
885 con_printf(CON_NORMAL, "Autosave written to \"%s\"", filename.data());
886 }
887 }
888
889 }
890
891 }
892
893 namespace dsx {
894
895 namespace {
896
read_savegame_properties(const std::size_t savegame_index,d_game_unique_state::savegame_file_path & filename,d_game_unique_state::savegame_description * const dsc,grs_bitmap_ptr * const sc_bmp)897 uint8_t read_savegame_properties(const std::size_t savegame_index, d_game_unique_state::savegame_file_path &filename, d_game_unique_state::savegame_description *const dsc, grs_bitmap_ptr *const sc_bmp)
898 {
899 state_format_savegame_filename(filename, savegame_index);
900 const auto fp = PHYSFSX_openReadBuffered(filename.data()).first;
901 if (!fp)
902 return 0;
903 //Read id
904 char id[4]{};
905 if (PHYSFS_read(fp, id, sizeof(id), 1) != 1)
906 return 0;
907 if (memcmp(id, dgss_id, 4))
908 return 0;
909 //Read version
910 unsigned version;
911 if (PHYSFS_read(fp, &version, sizeof(version), 1) != 1)
912 return 0;
913 if (!(version >= STATE_COMPATIBLE_VERSION || SWAPINT(version) >= STATE_COMPATIBLE_VERSION))
914 return 0;
915 // In case it's Coop, handle state_game_id & callsign as well
916 if (Game_mode & GM_MULTI_COOP)
917 {
918 PHYSFS_seek(fp, PHYSFS_tell(fp) + sizeof(PHYSFS_sint32) + sizeof(char)*CALLSIGN_LEN+1); // skip state_game_id, callsign
919 }
920 d_game_unique_state::savegame_description desc_storage;
921 d_game_unique_state::savegame_description &desc = dsc ? *dsc : desc_storage;
922 // Read description
923 if (PHYSFS_read(fp, desc.data(), desc.size(), 1) != 1)
924 return 0;
925 desc.back() = 0;
926 if (sc_bmp)
927 {
928 // Read thumbnail
929 grs_bitmap_ptr bmp = gr_create_bitmap(THUMBNAIL_W, THUMBNAIL_H);
930 if (PHYSFS_read(fp, bmp->get_bitmap_data(), THUMBNAIL_W * THUMBNAIL_H, 1) != 1)
931 return 0;
932 #if defined(DXX_BUILD_DESCENT_II)
933 if (version >= 9)
934 {
935 palette_array_t pal;
936 if (PHYSFS_read(fp, &pal[0], pal.size(), sizeof(pal[0])) != sizeof(pal[0]))
937 return 0;
938 gr_remap_bitmap_good(*bmp.get(), pal, -1, -1);
939 }
940 #endif
941 *sc_bmp = std::move(bmp);
942 }
943 return 1;
944 }
945
savegame_newmenu_items(d_game_unique_state::savegame_description * const savegame_description,d_game_unique_state::savegame_file_path & savegame_file_path,imenu_description_buffers_array * const user_entered_savegame_descriptions)946 savegame_newmenu_items::savegame_newmenu_items(d_game_unique_state::savegame_description *const savegame_description, d_game_unique_state::savegame_file_path &savegame_file_path, imenu_description_buffers_array *const user_entered_savegame_descriptions) :
947 user_entered_savegame_descriptions(user_entered_savegame_descriptions),
948 caller_savegame_description(savegame_description),
949 savegame_file_path(savegame_file_path)
950 {
951 unsigned nsaves = 0;
952 /* Always start at offset `decorative_item_count` to skip the fixed
953 * text leader. Conditionally subtract 1 if the call is for saving,
954 * since interactive saves should not access the last slot. The
955 * last slot is reserved for autosaves.
956 */
957 const unsigned max_slots_shown = get_count_valid_menuitem_entries(savegame_description);
958 for (const auto &&[savegame_index, mi, filename, desc, sc_bmp] : enumerate(zip(partial_range(m, decorative_item_count, max_slots_shown), savegame_file_paths, savegame_descriptions, sc_bmp)))
959 {
960 const auto existing_savegame_found = read_savegame_properties(savegame_index, filename, &desc, &sc_bmp);
961 if (existing_savegame_found)
962 ++nsaves;
963 else
964 /* Defer setting a default value to here. This allows the
965 * value to be written only if a better one was not
966 * retrieved from a save game file.
967 */
968 strcpy(desc.data(), TXT_EMPTY);
969 mi.text = desc.data();
970 mi.type = savegame_description
971 /* If saving, use input_menu so that the user can pick an
972 * element and convert it into a text entry field to receive
973 * the save game title.
974 */
975 ? nm_type::input_menu
976 /* If restoring, use text. Valid save games will switch the
977 * type. Invalid save slots will remain set as text.
978 */
979 : (existing_savegame_found
980 ? nm_type::menu
981 : nm_type::text);
982 if (user_entered_savegame_descriptions)
983 mi.initialize_imenu(desc, (*user_entered_savegame_descriptions)[savegame_index], nullptr);
984 }
985 if (!savegame_description && nsaves < 1)
986 throw error_no_saves_found();
987 nm_set_item_text(m[0], "\n\n\n\n");
988 }
989
990 }
991
state_set_immediate_autosave(d_game_unique_state & GameUniqueState)992 void state_set_immediate_autosave(d_game_unique_state &GameUniqueState)
993 {
994 GameUniqueState.Next_autosave = {};
995 }
996
state_set_next_autosave(d_game_unique_state & GameUniqueState,const std::chrono::steady_clock::time_point now,const autosave_interval_type interval)997 void state_set_next_autosave(d_game_unique_state &GameUniqueState, const std::chrono::steady_clock::time_point now, const autosave_interval_type interval)
998 {
999 GameUniqueState.Next_autosave = now + interval;
1000 }
1001
state_set_next_autosave(d_game_unique_state & GameUniqueState,const autosave_interval_type interval)1002 void state_set_next_autosave(d_game_unique_state &GameUniqueState, const autosave_interval_type interval)
1003 {
1004 const auto now = std::chrono::steady_clock::now();
1005 state_set_next_autosave(GameUniqueState, now, interval);
1006 }
1007
state_poll_autosave_game(d_game_unique_state & GameUniqueState,const d_level_unique_object_state & LevelUniqueObjectState)1008 void state_poll_autosave_game(d_game_unique_state &GameUniqueState, const d_level_unique_object_state &LevelUniqueObjectState)
1009 {
1010 const auto multiplayer = Game_mode & GM_MULTI;
1011 if (multiplayer && !multi_i_am_master())
1012 return;
1013 const auto interval = (multiplayer ? static_cast<const d_gameplay_options &>(Netgame.MPGameplayOptions) : PlayerCfg.SPGameplayOptions).AutosaveInterval;
1014 if (interval.count() <= 0)
1015 /* Autosave is disabled */
1016 return;
1017 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1018 auto &Objects = LevelUniqueObjectState.Objects;
1019 if (deny_save_game(Objects.vcptr, LevelUniqueControlCenterState, GameUniqueState) != deny_save_result::allowed)
1020 return;
1021 if (Newdemo_state != ND_STATE_NORMAL && Newdemo_state != ND_STATE_RECORDING)
1022 return;
1023 const auto now = std::chrono::steady_clock::now();
1024 if (now < GameUniqueState.Next_autosave)
1025 return;
1026 state_set_next_autosave(GameUniqueState, now, interval);
1027 state_autosave_game(multiplayer);
1028 }
1029
1030 /* Present a menu for selection of a savegame filename.
1031 * For saving, dsc should be a pre-allocated buffer into which the new
1032 * savegame description will be stored.
1033 * For restoring, dsc should be NULL, in which case empty slots will not be
1034 * selectable and savagames descriptions will not be editable.
1035 */
state_get_savegame_filename(grs_canvas & canvas,d_game_unique_state::savegame_file_path & fname,d_game_unique_state::savegame_description * const dsc,const menu_subtitle caption,const blind_save entry_blind)1036 static d_game_unique_state::save_slot state_get_savegame_filename(grs_canvas &canvas, d_game_unique_state::savegame_file_path &fname, d_game_unique_state::savegame_description *const dsc, const menu_subtitle caption, const blind_save entry_blind)
1037 {
1038 const auto quicksave_selection = GameUniqueState.quicksave_selection;
1039 if (entry_blind != blind_save::no &&
1040 /* The user requested a non-blind save. This block only handles
1041 * blind saves, so skip down to the general handler.
1042 */
1043 savegame_chooser_newmenu::valid_savegame_index(dsc, quicksave_selection))
1044 {
1045 /* The cached slot is valid, so a save/load might work.
1046 */
1047 if (read_savegame_properties(static_cast<std::size_t>(quicksave_selection), fname, dsc, nullptr))
1048 /* The data was read from the savegame. Return early and
1049 * skip the dialog.
1050 */
1051 return quicksave_selection;
1052 /* Fall through and run the interactive dialog, whether the user
1053 * requested one or not.
1054 */
1055 }
1056 std::unique_ptr<savegame_chooser_newmenu> win;
1057 try {
1058 win = savegame_chooser_newmenu::create(caption, canvas, dsc, fname);
1059 }
1060 catch (const savegame_chooser_newmenu::error_no_saves_found &)
1061 {
1062 struct error_no_saves_found : passive_messagebox
1063 {
1064 error_no_saves_found(grs_canvas &canvas) :
1065 passive_messagebox(menu_title{nullptr}, menu_subtitle{"No saved games were found!"}, TXT_OK, canvas)
1066 {
1067 }
1068 };
1069 run_blocking_newmenu<error_no_saves_found>(canvas);
1070 return d_game_unique_state::save_slot::None;
1071 }
1072 win->send_creation_events();
1073 const auto citem = savegame_chooser_newmenu::process_until_closed(win.release());
1074 /* win is now invalid */
1075 if (citem <= 0)
1076 return d_game_unique_state::save_slot::None;
1077 const auto choice = static_cast<d_game_unique_state::save_slot>(citem - savegame_chooser_newmenu::decorative_item_count);
1078 assert(savegame_chooser_newmenu::valid_savegame_index(dsc, choice));
1079 return choice;
1080 }
1081
state_get_save_file(grs_canvas & canvas,d_game_unique_state::savegame_file_path & fname,d_game_unique_state::savegame_description * const dsc,const blind_save blind_save)1082 d_game_unique_state::save_slot state_get_save_file(grs_canvas &canvas, d_game_unique_state::savegame_file_path &fname, d_game_unique_state::savegame_description *const dsc, const blind_save blind_save)
1083 {
1084 return state_get_savegame_filename(canvas, fname, dsc, menu_subtitle{"Save Game"}, blind_save);
1085 }
1086
state_get_restore_file(grs_canvas & canvas,d_game_unique_state::savegame_file_path & fname,blind_save blind_save)1087 d_game_unique_state::save_slot state_get_restore_file(grs_canvas &canvas, d_game_unique_state::savegame_file_path &fname, blind_save blind_save)
1088 {
1089 return state_get_savegame_filename(canvas, fname, nullptr, menu_subtitle{"Select Game to Restore"}, blind_save);
1090 }
1091
1092 #if defined(DXX_BUILD_DESCENT_I)
1093 #elif defined(DXX_BUILD_DESCENT_II)
1094
1095 // -----------------------------------------------------------------------------------
1096 // Imagine if C had a function to copy a file...
copy_file(const char * old_file,const char * new_file)1097 static int copy_file(const char *old_file, const char *new_file)
1098 {
1099 RAIIPHYSFS_File in_file{PHYSFS_openRead(old_file)};
1100 if (!in_file)
1101 return -2;
1102 RAIIPHYSFS_File out_file{PHYSFS_openWrite(new_file)};
1103 if (!out_file)
1104 return -1;
1105
1106 constexpr std::size_t buf_size = 512 * 1024;
1107 const auto buf = std::make_unique<uint8_t[]>(buf_size);
1108 const auto pbuf = buf.get();
1109 while (!PHYSFS_eof(in_file))
1110 {
1111 const auto bytes_read = PHYSFS_read(in_file, pbuf, 1, buf_size);
1112 if (bytes_read < 0)
1113 Error("Cannot read from file <%s>: %s", old_file, PHYSFS_getLastError());
1114
1115 if (PHYSFS_write(out_file, pbuf, 1, bytes_read) < bytes_read)
1116 Error("Cannot write to file <%s>: %s", new_file, PHYSFS_getLastError());
1117 }
1118 if (!out_file.close())
1119 return -4;
1120
1121 return 0;
1122 }
1123
format_secret_sgc_filename(std::array<char,PATH_MAX> & fname,const d_game_unique_state::save_slot filenum)1124 static void format_secret_sgc_filename(std::array<char, PATH_MAX> &fname, const d_game_unique_state::save_slot filenum)
1125 {
1126 snprintf(fname.data(), fname.size(), PLAYER_DIRECTORY_STRING("%xsecret.sgc"), static_cast<unsigned>(filenum));
1127 }
1128 #endif
1129
1130 // -----------------------------------------------------------------------------------
1131 #if defined(DXX_BUILD_DESCENT_I)
state_save_all(const blind_save blind_save)1132 int state_save_all(const blind_save blind_save)
1133 #elif defined(DXX_BUILD_DESCENT_II)
1134 int state_save_all(const secret_save secret, const blind_save blind_save)
1135 #endif
1136 {
1137 #if defined(DXX_BUILD_DESCENT_I)
1138 static constexpr std::integral_constant<secret_save, secret_save::none> secret{};
1139 #elif defined(DXX_BUILD_DESCENT_II)
1140 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1141 if (Current_level_num < 0 && secret == secret_save::none)
1142 {
1143 HUD_init_message_literal(HM_DEFAULT, "Can't save in secret level!" );
1144 return 0;
1145 }
1146
1147 if (GameUniqueState.Final_boss_countdown_time) //don't allow save while final boss is dying
1148 return 0;
1149 #endif
1150
1151 if ( Game_mode & GM_MULTI )
1152 {
1153 if (Game_mode & GM_MULTI_COOP)
1154 multi_initiate_save_game();
1155 return 0;
1156 }
1157
1158 #if defined(DXX_BUILD_DESCENT_II)
1159 // If this is a secret save and the control center has been destroyed, don't allow
1160 // return to the base level.
1161 if (secret != secret_save::none && LevelUniqueControlCenterState.Control_center_destroyed)
1162 {
1163 PHYSFS_delete(SECRETB_FILENAME);
1164 return 0;
1165 }
1166 #endif
1167
1168 d_game_unique_state::savegame_file_path filename_storage;
1169 const char *filename;
1170 d_game_unique_state::savegame_description desc{};
1171 d_game_unique_state::save_slot filenum = d_game_unique_state::save_slot::None;
1172 {
1173 pause_game_world_time p;
1174
1175 #if defined(DXX_BUILD_DESCENT_II)
1176 if (secret == secret_save::b) {
1177 filename = SECRETB_FILENAME;
1178 } else if (secret == secret_save::c) {
1179 filename = SECRETC_FILENAME;
1180 } else
1181 #endif
1182 {
1183 filenum = state_get_save_file(*grd_curcanv, filename_storage, &desc, blind_save);
1184 if (!GameUniqueState.valid_save_slot(filenum))
1185 return 0;
1186 filename = filename_storage.data();
1187 }
1188 #if defined(DXX_BUILD_DESCENT_II)
1189 // MK, 1/1/96
1190 // Do special secret level stuff.
1191 // If secret.sgc exists, then copy it to Nsecret.sgc (where N = filenum).
1192 // If it doesn't exist, then delete Nsecret.sgc
1193 if (secret == secret_save::none && !(Game_mode & GM_MULTI_COOP)) {
1194 if (filenum != d_game_unique_state::save_slot::None)
1195 {
1196 std::array<char, PATH_MAX> fname;
1197 const auto temp_fname = fname.data();
1198 format_secret_sgc_filename(fname, filenum);
1199 if (PHYSFSX_exists(temp_fname,0))
1200 {
1201 if (!PHYSFS_delete(temp_fname))
1202 Error("Cannot delete file <%s>: %s", temp_fname, PHYSFS_getLastError());
1203 }
1204
1205 if (PHYSFSX_exists(SECRETC_FILENAME,0))
1206 {
1207 const int rval = copy_file(SECRETC_FILENAME, temp_fname);
1208 Assert(rval == 0); // Oops, error copying secret.sgc to temp_fname!
1209 (void)rval;
1210 }
1211 }
1212 }
1213 #endif
1214 }
1215
1216 const int rval = state_save_all_sub(filename, desc.data());
1217
1218 if (rval && secret == secret_save::none)
1219 HUD_init_message(HM_DEFAULT, "Game saved to \"%s\": \"%s\"", filename, desc.data());
1220
1221 return rval;
1222 }
1223
state_save_all_sub(const char * filename,const char * desc)1224 int state_save_all_sub(const char *filename, const char *desc)
1225 {
1226 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1227 auto &Objects = LevelUniqueObjectState.Objects;
1228 auto &vcobjptr = Objects.vcptr;
1229 auto &vmobjptr = Objects.vmptr;
1230 auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
1231 auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
1232 auto &Station = LevelUniqueFuelcenterState.Station;
1233 fix tmptime32 = 0;
1234
1235 #ifndef NDEBUG
1236 if (CGameArg.SysUsePlayersDir && strncmp(filename, PLAYER_DIRECTORY_TEXT, sizeof(PLAYER_DIRECTORY_TEXT) - 1))
1237 Int3();
1238 #endif
1239
1240 auto &&[fp, physfserr] = PHYSFSX_openWriteBuffered(filename);
1241 if (!fp)
1242 {
1243 const auto errstr = PHYSFS_getErrorByCode(physfserr);
1244 con_printf(CON_URGENT, "Failed to open %s: %s", filename, errstr);
1245 struct error_writing_savegame :
1246 std::array<char, 96>,
1247 passive_messagebox
1248 {
1249 error_writing_savegame(const char *filename, const char *errstr) :
1250 passive_messagebox(menu_title{TXT_ERROR}, menu_subtitle{prepare_subtitle(*this, filename, errstr)}, "Return to unsaved game", grd_curscreen->sc_canvas)
1251 {
1252 }
1253 static const char *prepare_subtitle(std::array<char, 96> &b, const char *filename, const char *errstr)
1254 {
1255 auto r = b.data();
1256 std::snprintf(r, b.size(), "Failed to write savegame\n%s\n\n%s", filename, errstr);
1257 return r;
1258 }
1259 };
1260 run_blocking_newmenu<error_writing_savegame>(filename, errstr);
1261 return 0;
1262 }
1263
1264 pause_game_world_time p;
1265
1266 //Save id
1267 PHYSFS_write(fp, dgss_id, sizeof(char) * 4, 1);
1268
1269 //Save version
1270 {
1271 const int i = STATE_VERSION;
1272 PHYSFS_write(fp, &i, sizeof(int), 1);
1273 }
1274
1275 // Save Coop state_game_id and this Player's callsign. Oh the redundancy... we have this one later on but Coop games want to read this before loading a state so for easy access save this here, too
1276 if (Game_mode & GM_MULTI_COOP)
1277 {
1278 PHYSFS_write(fp, &state_game_id, sizeof(unsigned), 1);
1279 PHYSFS_write(fp, &get_local_player().callsign, sizeof(char)*CALLSIGN_LEN+1, 1);
1280 }
1281
1282 //Save description
1283 PHYSFS_write(fp, desc, 20, 1);
1284
1285 // Save the current screen shot...
1286
1287 auto cnv = gr_create_canvas( THUMBNAIL_W, THUMBNAIL_H );
1288 {
1289 {
1290 window_rendered_data window;
1291 render_frame(*cnv, 0, window);
1292 }
1293
1294 {
1295 #if DXX_USE_OGL
1296 const auto buf = std::make_unique<uint8_t[]>(THUMBNAIL_W * THUMBNAIL_H * 4);
1297 #if !DXX_USE_OGLES
1298 GLint gl_draw_buffer;
1299 glGetIntegerv(GL_DRAW_BUFFER, &gl_draw_buffer);
1300 glReadBuffer(gl_draw_buffer);
1301 #endif
1302 glReadPixels(0, SHEIGHT - THUMBNAIL_H, THUMBNAIL_W, THUMBNAIL_H, GL_RGBA, GL_UNSIGNED_BYTE, buf.get());
1303 int k;
1304 k = THUMBNAIL_H;
1305 for (unsigned i = 0; i < THUMBNAIL_W * THUMBNAIL_H; i++)
1306 {
1307 int j;
1308 if (!(j = i % THUMBNAIL_W))
1309 k--;
1310 cnv->cv_bitmap.get_bitmap_data()[THUMBNAIL_W * k + j] =
1311 gr_find_closest_color(buf[4*i]/4, buf[4*i+1]/4, buf[4*i+2]/4);
1312 }
1313 #endif
1314 }
1315
1316 PHYSFS_write(fp, cnv->cv_bitmap.bm_data, THUMBNAIL_W * THUMBNAIL_H, 1);
1317 #if defined(DXX_BUILD_DESCENT_II)
1318 PHYSFS_write(fp, &gr_palette[0], sizeof(gr_palette[0]), gr_palette.size());
1319 #endif
1320 }
1321
1322 // Save the Between levels flag...
1323 {
1324 const int i = 0;
1325 PHYSFS_write(fp, &i, sizeof(int), 1);
1326 }
1327
1328 // Save the mission info...
1329 savegame_mission_path mission_pathname{};
1330 #if defined(DXX_BUILD_DESCENT_II)
1331 mission_pathname.original[1] = static_cast<uint8_t>(Current_mission->descent_version);
1332 #endif
1333 mission_pathname.original.back() = static_cast<uint8_t>(savegame_mission_name_abi::pathname);
1334 auto Current_mission_pathname = Current_mission->path.c_str();
1335 // Current_mission_filename is not necessarily 9 bytes long so for saving we use a proper string - preventing corruptions
1336 snprintf(mission_pathname.full.data(), mission_pathname.full.size(), "%s", Current_mission_pathname);
1337 PHYSFS_write(fp, &mission_pathname, sizeof(mission_pathname), 1);
1338
1339 //Save level info
1340 PHYSFS_write(fp, &Current_level_num, sizeof(int), 1);
1341 PHYSFS_writeULE32(fp, 0);
1342
1343 //Save GameTime
1344 // NOTE: GameTime now is GameTime64 with fix64 since GameTime could only last 9 hrs. To even help old Savegames, we do not increment Savegame version but rather RESET GameTime64 to 0 on every save! ALL variables based on GameTime64 now will get the current GameTime64 value substracted and saved to fix size as well.
1345 tmptime32 = 0;
1346 PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
1347
1348 //Save player info
1349 //PHYSFS_write(fp, &Players[Player_num], sizeof(player), 1);
1350 const auto &plrobj = get_local_plrobj();
1351 auto &player_info = plrobj.ctype.player_info;
1352 state_write_player(fp, get_local_player(), relocated_player_data{
1353 plrobj.shields,
1354 static_cast<int16_t>(LevelUniqueObjectState.accumulated_robots),
1355 static_cast<int16_t>(GameUniqueState.accumulated_robots),
1356 static_cast<uint16_t>(GameUniqueState.total_hostages),
1357 static_cast<uint8_t>(LevelUniqueObjectState.total_hostages)
1358 }, player_info);
1359
1360 // Save the current weapon info
1361 {
1362 int8_t v = static_cast<int8_t>(static_cast<primary_weapon_index_t>(player_info.Primary_weapon));
1363 PHYSFS_write(fp, &v, sizeof(int8_t), 1);
1364 }
1365 {
1366 int8_t v = static_cast<int8_t>(static_cast<secondary_weapon_index_t>(player_info.Secondary_weapon));
1367 PHYSFS_write(fp, &v, sizeof(int8_t), 1);
1368 }
1369
1370 // Save the difficulty level
1371 {
1372 const int Difficulty_level = GameUniqueState.Difficulty_level;
1373 PHYSFS_write(fp, &Difficulty_level, sizeof(int), 1);
1374 }
1375
1376 // Save cheats enabled
1377 PHYSFS_write(fp, &cheats.enabled, sizeof(int), 1);
1378 #if defined(DXX_BUILD_DESCENT_I)
1379 PHYSFS_write(fp, &cheats.turbo, sizeof(int), 1);
1380 #endif
1381
1382 //Finish all morph objects
1383 range_for (const auto &&objp, vmobjptr)
1384 {
1385 if (objp->type != OBJ_NONE && objp->render_type == RT_MORPH)
1386 {
1387 if (const auto umd = find_morph_data(LevelUniqueMorphObjectState, objp))
1388 {
1389 const auto md = umd->get();
1390 md->obj->control_source = md->morph_save_control_type;
1391 md->obj->movement_source = md->morph_save_movement_type;
1392 md->obj->render_type = RT_POLYOBJ;
1393 md->obj->mtype.phys_info = md->morph_save_phys_info;
1394 umd->reset();
1395 } else { //maybe loaded half-morphed from disk
1396 objp->flags |= OF_SHOULD_BE_DEAD;
1397 objp->render_type = RT_POLYOBJ;
1398 objp->control_source = object::control_type::None;
1399 objp->movement_source = object::movement_type::None;
1400 }
1401 }
1402 }
1403
1404 //Save object info
1405 {
1406 const int i = Highest_object_index+1;
1407 PHYSFS_write(fp, &i, sizeof(int), 1);
1408 }
1409 {
1410 object_rw None{};
1411 None.type = OBJ_NONE;
1412 range_for (const auto &&objp, vcobjptr)
1413 {
1414 object_rw obj_rw;
1415 auto &obj = *objp;
1416 PHYSFS_write(fp, obj.type == OBJ_NONE ? &None : (state_object_to_object_rw(objp, &obj_rw), &obj_rw), sizeof(obj_rw), 1);
1417 }
1418 }
1419
1420 //Save wall info
1421 {
1422 auto &Walls = LevelUniqueWallSubsystemState.Walls;
1423 auto &vcwallptr = Walls.vcptr;
1424 {
1425 const int i = Walls.get_count();
1426 PHYSFS_write(fp, &i, sizeof(int), 1);
1427 }
1428 range_for (const auto &&w, vcwallptr)
1429 wall_write(fp, *w, 0x7fff);
1430
1431 #if defined(DXX_BUILD_DESCENT_II)
1432 //Save exploding wall info
1433 expl_wall_write(Walls.vmptr, fp);
1434 #endif
1435 }
1436
1437 //Save door info
1438 {
1439 auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
1440 {
1441 const int i = ActiveDoors.get_count();
1442 PHYSFS_write(fp, &i, sizeof(int), 1);
1443 }
1444 range_for (auto &&ad, ActiveDoors.vcptr)
1445 active_door_write(fp, ad);
1446 }
1447
1448 #if defined(DXX_BUILD_DESCENT_II)
1449 //Save cloaking wall info
1450 {
1451 auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls;
1452 {
1453 const int i = CloakingWalls.get_count();
1454 PHYSFS_write(fp, &i, sizeof(int), 1);
1455 }
1456 range_for (auto &&w, CloakingWalls.vcptr)
1457 cloaking_wall_write(w, fp);
1458 }
1459 #endif
1460
1461 //Save trigger info
1462 {
1463 auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
1464 {
1465 unsigned num_triggers = Triggers.get_count();
1466 PHYSFS_write(fp, &num_triggers, sizeof(int), 1);
1467 }
1468 range_for (const trigger &vt, Triggers.vcptr)
1469 trigger_write(fp, vt);
1470 }
1471
1472 //Save tmap info
1473 range_for (const auto &&segp, vcsegptr)
1474 {
1475 for (auto &&[ss, us] : zip(segp->shared_segment::sides, segp->unique_segment::sides)) // d_zip
1476 {
1477 segment_side_wall_tmap_write(fp, ss, us);
1478 }
1479 }
1480
1481 // Save the fuelcen info
1482 {
1483 const int Control_center_destroyed = LevelUniqueControlCenterState.Control_center_destroyed;
1484 PHYSFS_write(fp, &Control_center_destroyed, sizeof(int), 1);
1485 }
1486 #if defined(DXX_BUILD_DESCENT_I)
1487 PHYSFS_write(fp, &LevelUniqueControlCenterState.Countdown_seconds_left, sizeof(int), 1);
1488 #elif defined(DXX_BUILD_DESCENT_II)
1489 PHYSFS_write(fp, &LevelUniqueControlCenterState.Countdown_timer, sizeof(int), 1);
1490 #endif
1491 const unsigned Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers;
1492 PHYSFS_write(fp, &Num_robot_centers, sizeof(int), 1);
1493 range_for (auto &r, partial_const_range(RobotCenters, Num_robot_centers))
1494 #if defined(DXX_BUILD_DESCENT_I)
1495 matcen_info_write(fp, r, STATE_MATCEN_VERSION);
1496 #elif defined(DXX_BUILD_DESCENT_II)
1497 matcen_info_write(fp, r, 0x7f);
1498 #endif
1499 control_center_triggers_write(&ControlCenterTriggers, fp);
1500 const auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters;
1501 PHYSFS_write(fp, &Num_fuelcenters, sizeof(int), 1);
1502 range_for (auto &s, partial_range(Station, Num_fuelcenters))
1503 {
1504 #if defined(DXX_BUILD_DESCENT_I)
1505 // NOTE: Usually Descent1 handles countdown by Timer value of the Reactor Station. Since we now use Descent2 code to handle countdown (which we do in case there IS NO Reactor Station which causes potential trouble in Multiplayer), let's find the Reactor here and store the timer in it.
1506 if (s.Type == segment_special::controlcen)
1507 s.Timer = LevelUniqueControlCenterState.Countdown_timer;
1508 #endif
1509 fuelcen_write(fp, s);
1510 }
1511
1512 // Save the control cen info
1513 {
1514 const int Control_center_been_hit = LevelUniqueControlCenterState.Control_center_been_hit;
1515 PHYSFS_write(fp, &Control_center_been_hit, sizeof(int), 1);
1516 }
1517 {
1518 const auto cc = static_cast<int>(LevelUniqueControlCenterState.Control_center_player_been_seen);
1519 PHYSFS_write(fp, &cc, sizeof(int), 1);
1520 }
1521 PHYSFS_write(fp, &LevelUniqueControlCenterState.Frametime_until_next_fire, sizeof(int), 1);
1522 {
1523 const int Control_center_present = LevelUniqueControlCenterState.Control_center_present;
1524 PHYSFS_write(fp, &Control_center_present, sizeof(int), 1);
1525 }
1526 {
1527 const auto Dead_controlcen_object_num = LevelUniqueControlCenterState.Dead_controlcen_object_num;
1528 int dead_controlcen_object_num = Dead_controlcen_object_num == object_none ? -1 : Dead_controlcen_object_num;
1529 PHYSFS_write(fp, &dead_controlcen_object_num, sizeof(int), 1);
1530 }
1531
1532 // Save the AI state
1533 ai_save_state( fp );
1534
1535 // Save the automap visited info
1536 PHYSFS_write(fp, LevelUniqueAutomapState.Automap_visited.data(), sizeof(uint8_t), std::max<std::size_t>(Highest_segment_index + 1, MAX_SEGMENTS_ORIGINAL));
1537
1538 PHYSFS_write(fp, &state_game_id, sizeof(unsigned), 1);
1539 {
1540 const int i = 0;
1541 PHYSFS_write(fp, &cheats.rapidfire, sizeof(int), 1);
1542 #if defined(DXX_BUILD_DESCENT_I)
1543 PHYSFS_write(fp, &i, sizeof(int), 1); // was Ugly_robot_cheat
1544 PHYSFS_write(fp, &i, sizeof(int), 1); // was Ugly_robot_texture
1545 PHYSFS_write(fp, &cheats.ghostphysics, sizeof(int), 1);
1546 #endif
1547 PHYSFS_write(fp, &i, sizeof(int), 1); // was Lunacy
1548 #if defined(DXX_BUILD_DESCENT_II)
1549 PHYSFS_write(fp, &i, sizeof(int), 1); // was Lunacy, too... and one was Ugly robot stuff a long time ago...
1550
1551 // Save automap marker info
1552
1553 range_for (int m, MarkerState.imobjidx)
1554 {
1555 if (m == object_none)
1556 m = -1;
1557 PHYSFS_write(fp, &m, sizeof(m), 1);
1558 }
1559 PHYSFS_seek(fp, PHYSFS_tell(fp) + (NUM_MARKERS)*(CALLSIGN_LEN+1)); // PHYSFS_write(fp, MarkerOwner, sizeof(MarkerOwner), 1); MarkerOwner is obsolete
1560 range_for (const auto &m, MarkerState.message)
1561 PHYSFS_write(fp, m.data(), m.size(), 1);
1562
1563 PHYSFS_write(fp, &Afterburner_charge, sizeof(fix), 1);
1564
1565 //save last was super information
1566 {
1567 auto &Primary_last_was_super = player_info.Primary_last_was_super;
1568 std::array<uint8_t, MAX_PRIMARY_WEAPONS> last_was_super{};
1569 /* Descent 2 shipped with Primary_last_was_super and
1570 * Secondary_last_was_super each sized to contain MAX_*_WEAPONS,
1571 * but only the first half of those are ever used.
1572 * Unfortunately, the save file format is defined as saving
1573 * MAX_*_WEAPONS for each. Copy into a temporary, then write
1574 * the temporary to the file.
1575 */
1576 for (uint_fast32_t j = primary_weapon_index_t::VULCAN_INDEX; j != primary_weapon_index_t::SUPER_LASER_INDEX; ++j)
1577 {
1578 if (Primary_last_was_super & (1 << j))
1579 last_was_super[j] = 1;
1580 }
1581 PHYSFS_write(fp, &last_was_super, MAX_PRIMARY_WEAPONS, 1);
1582 auto &Secondary_last_was_super = player_info.Secondary_last_was_super;
1583 for (uint_fast32_t j = secondary_weapon_index_t::CONCUSSION_INDEX; j != secondary_weapon_index_t::SMISSILE1_INDEX; ++j)
1584 {
1585 if (Secondary_last_was_super & (1 << j))
1586 last_was_super[j] = 1;
1587 }
1588 PHYSFS_write(fp, &last_was_super, MAX_SECONDARY_WEAPONS, 1);
1589 }
1590
1591 // Save flash effect stuff
1592 PHYSFS_write(fp, &Flash_effect, sizeof(int), 1);
1593 if (Time_flash_last_played - GameTime64 < F1_0*(-18000))
1594 tmptime32 = F1_0*(-18000);
1595 else
1596 tmptime32 = Time_flash_last_played - GameTime64;
1597 PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
1598 PHYSFS_write(fp, &PaletteRedAdd, sizeof(int), 1);
1599 PHYSFS_write(fp, &PaletteGreenAdd, sizeof(int), 1);
1600 PHYSFS_write(fp, &PaletteBlueAdd, sizeof(int), 1);
1601 {
1602 union {
1603 std::array<uint8_t, MAX_SEGMENTS> light_subtracted;
1604 std::array<uint8_t, MAX_SEGMENTS_ORIGINAL> light_subtracted_original;
1605 };
1606 const auto &&r = make_range(vcsegptr);
1607 /* For compatibility with old game versions, always write at
1608 * least MAX_SEGMENTS_ORIGINAL entries. If the level is larger
1609 * than that, then write as many entries as needed for the
1610 * level.
1611 */
1612 const unsigned count = (Highest_segment_index + 1 > MAX_SEGMENTS_ORIGINAL)
1613 /* Every written element will be filled by the loop, so
1614 * there is no need to initialize the storage area.
1615 */
1616 ? vcsegptr.count()
1617 /* The loop may fill fewer than MAX_SEGMENTS_ORIGINAL
1618 * entries, but MAX_SEGMENTS_ORIGINAL entries will be
1619 * written, so zero-initialize the elements first.
1620 */
1621 : (light_subtracted_original = {}, MAX_SEGMENTS_ORIGINAL);
1622 auto j = light_subtracted.begin();
1623 for (const unique_segment &useg : r)
1624 *j++ = useg.light_subtracted;
1625 PHYSFS_write(fp, light_subtracted.data(), sizeof(uint8_t), count);
1626 }
1627 PHYSFS_write(fp, &First_secret_visit, sizeof(First_secret_visit), 1);
1628 auto &Omega_charge = player_info.Omega_charge;
1629 PHYSFS_write(fp, &Omega_charge, sizeof(Omega_charge), 1);
1630 #endif
1631 }
1632
1633 // Save Coop Info
1634 if (Game_mode & GM_MULTI_COOP)
1635 {
1636 /* Write local player's shields for everyone. Remote players'
1637 * shields are ignored, and using local everywhere is cheaper
1638 * than using it only for the one slot where it may matter.
1639 */
1640 const auto shields = plrobj.shields;
1641 const relocated_player_data rpd{shields, 0, 0, 0, 0};
1642 // I know, I know we only allow 4 players in coop. I screwed that up. But if we ever allow 8 players in coop, who's gonna laugh then?
1643 range_for (auto &i, partial_const_range(Players, MAX_PLAYERS))
1644 {
1645 state_write_player(fp, i, rpd, player_info);
1646 }
1647 PHYSFS_write(fp, Netgame.mission_title.data(), Netgame.mission_title.size(), 1);
1648 PHYSFS_write(fp, Netgame.mission_name.data(), Netgame.mission_name.size(), 1);
1649 PHYSFS_write(fp, &Netgame.levelnum, sizeof(int), 1);
1650 PHYSFS_write(fp, &Netgame.difficulty, sizeof(ubyte), 1);
1651 PHYSFS_write(fp, &Netgame.game_status, sizeof(ubyte), 1);
1652 PHYSFS_write(fp, &Netgame.numplayers, sizeof(ubyte), 1);
1653 PHYSFS_write(fp, &Netgame.max_numplayers, sizeof(ubyte), 1);
1654 PHYSFS_write(fp, &Netgame.numconnected, sizeof(ubyte), 1);
1655 PHYSFS_write(fp, &Netgame.level_time, sizeof(int), 1);
1656 }
1657 return 1;
1658 }
1659
1660 // -----------------------------------------------------------------------------------
1661 // Set the player's position from the globals Secret_return_segment and Secret_return_orient.
1662 #if defined(DXX_BUILD_DESCENT_II)
set_pos_from_return_segment(void)1663 void set_pos_from_return_segment(void)
1664 {
1665 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1666 auto &Objects = LevelUniqueObjectState.Objects;
1667 auto &Vertices = LevelSharedVertexState.get_vertices();
1668 auto &vmobjptr = Objects.vmptr;
1669 auto &vmobjptridx = Objects.vmptridx;
1670 const auto &&plobjnum = vmobjptridx(get_local_player().objnum);
1671 const auto &&segp = vmsegptridx(LevelSharedSegmentState.Secret_return_segment);
1672 auto &vcvertptr = Vertices.vcptr;
1673 compute_segment_center(vcvertptr, plobjnum->pos, segp);
1674 obj_relink(vmobjptr, vmsegptr, plobjnum, segp);
1675 reset_player_object();
1676 plobjnum->orient = LevelSharedSegmentState.Secret_return_orient;
1677 }
1678 #endif
1679
1680 // -----------------------------------------------------------------------------------
1681 #if defined(DXX_BUILD_DESCENT_I)
state_restore_all(const int in_game,std::nullptr_t,const blind_save blind)1682 int state_restore_all(const int in_game, std::nullptr_t, const blind_save blind)
1683 #elif defined(DXX_BUILD_DESCENT_II)
1684 int state_restore_all(const int in_game, const secret_restore secret, const char *const filename_override, const blind_save blind)
1685 #endif
1686 {
1687
1688 #if defined(DXX_BUILD_DESCENT_I)
1689 static constexpr std::integral_constant<secret_restore, secret_restore::none> secret{};
1690 #elif defined(DXX_BUILD_DESCENT_II)
1691 if (in_game && Current_level_num < 0 && secret == secret_restore::none)
1692 {
1693 HUD_init_message_literal(HM_DEFAULT, "Can't restore in secret level!" );
1694 return 0;
1695 }
1696 #endif
1697
1698 if ( Newdemo_state == ND_STATE_RECORDING )
1699 newdemo_stop_recording();
1700
1701 if ( Newdemo_state != ND_STATE_NORMAL )
1702 return 0;
1703
1704 if ( Game_mode & GM_MULTI )
1705 {
1706 if (Game_mode & GM_MULTI_COOP)
1707 multi_initiate_restore_game();
1708 return 0;
1709 }
1710
1711 d_game_unique_state::savegame_file_path filename_storage;
1712 const char *filename;
1713 d_game_unique_state::save_slot filenum = d_game_unique_state::save_slot::None;
1714 {
1715 pause_game_world_time p;
1716
1717 #if defined(DXX_BUILD_DESCENT_II)
1718 if (filename_override) {
1719 filename = filename_override;
1720 filenum = d_game_unique_state::save_slot::secret_save_filename_override; // place outside of save slots
1721 } else
1722 #endif
1723 {
1724 filenum = state_get_restore_file(*grd_curcanv, filename_storage, blind);
1725 if (!GameUniqueState.valid_load_slot(filenum))
1726 {
1727 return 0;
1728 }
1729 filename = filename_storage.data();
1730 }
1731 #if defined(DXX_BUILD_DESCENT_II)
1732 // MK, 1/1/96
1733 // Do special secret level stuff.
1734 // If Nsecret.sgc (where N = filenum) exists, then copy it to secret.sgc.
1735 // If it doesn't exist, then delete secret.sgc
1736 if (secret == secret_restore::none)
1737 {
1738 int rval;
1739
1740 if (filenum != d_game_unique_state::save_slot::None)
1741 {
1742 std::array<char, PATH_MAX> fname;
1743 const auto temp_fname = fname.data();
1744 format_secret_sgc_filename(fname, filenum);
1745 if (PHYSFSX_exists(temp_fname,0))
1746 {
1747 rval = copy_file(temp_fname, SECRETC_FILENAME);
1748 Assert(rval == 0); // Oops, error copying temp_fname to secret.sgc!
1749 (void)rval;
1750 } else
1751 PHYSFS_delete(SECRETC_FILENAME);
1752 }
1753 }
1754 #endif
1755 if (secret == secret_restore::none && in_game && blind == blind_save::no)
1756 {
1757 const auto choice = nm_messagebox_str(menu_title{nullptr}, nm_messagebox_tie(TXT_YES, TXT_NO), menu_subtitle{"Restore Game?"});
1758 if ( choice != 0 ) {
1759 return 0;
1760 }
1761 }
1762 }
1763 return state_restore_all_sub(
1764 #if defined(DXX_BUILD_DESCENT_II)
1765 LevelSharedSegmentState.DestructibleLights, secret,
1766 #endif
1767 filename);
1768 }
1769
1770 #if defined(DXX_BUILD_DESCENT_I)
state_restore_all_sub(const char * filename)1771 int state_restore_all_sub(const char *filename)
1772 #elif defined(DXX_BUILD_DESCENT_II)
1773 int state_restore_all_sub(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, const secret_restore secret, const char *const filename)
1774 #endif
1775 {
1776 auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1777 auto &Objects = LevelUniqueObjectState.Objects;
1778 auto &vmobjptr = Objects.vmptr;
1779 auto &vmobjptridx = Objects.vmptridx;
1780 auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
1781 auto &Station = LevelUniqueFuelcenterState.Station;
1782 int version, coop_player_got[MAX_PLAYERS], coop_org_objnum = get_local_player().objnum;
1783 int swap = 0; // if file is not endian native, have to swap all shorts and ints
1784 int current_level;
1785 char id[5];
1786 fix tmptime32 = 0;
1787 std::array<std::array<texture1_value, MAX_SIDES_PER_SEGMENT>, MAX_SEGMENTS> TempTmapNum;
1788 std::array<std::array<texture2_value, MAX_SIDES_PER_SEGMENT>, MAX_SEGMENTS> TempTmapNum2;
1789
1790 #if defined(DXX_BUILD_DESCENT_I)
1791 static constexpr std::integral_constant<secret_restore, secret_restore::none> secret{};
1792 #elif defined(DXX_BUILD_DESCENT_II)
1793 auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
1794 fix64 old_gametime = GameTime64;
1795 #endif
1796
1797 #ifndef NDEBUG
1798 if (CGameArg.SysUsePlayersDir && strncmp(filename, PLAYER_DIRECTORY_TEXT, sizeof(PLAYER_DIRECTORY_TEXT) - 1))
1799 Int3();
1800 #endif
1801
1802 auto fp = PHYSFSX_openReadBuffered(filename).first;
1803 if ( !fp ) return 0;
1804
1805 //Read id
1806 PHYSFS_read(fp, id, sizeof(char) * 4, 1);
1807 if ( memcmp( id, dgss_id, 4 )) {
1808 return 0;
1809 }
1810
1811 //Read version
1812 //Check for swapped file here, as dgss_id is written as a string (i.e. endian independent)
1813 PHYSFS_read(fp, &version, sizeof(int), 1);
1814 if (version & 0xffff0000)
1815 {
1816 swap = 1;
1817 version = SWAPINT(version);
1818 }
1819
1820 if (version < STATE_COMPATIBLE_VERSION) {
1821 return 0;
1822 }
1823
1824 // Read Coop state_game_id. Oh the redundancy... we have this one later on but Coop games want to read this before loading a state so for easy access we have this here
1825 if (Game_mode & GM_MULTI_COOP)
1826 {
1827 callsign_t saved_callsign;
1828 state_game_id = PHYSFSX_readSXE32(fp, swap);
1829 PHYSFS_read(fp, &saved_callsign, sizeof(char)*CALLSIGN_LEN+1, 1);
1830 if (!(saved_callsign == get_local_player().callsign)) // check the callsign of the palyer who saved this state. It MUST match. If we transferred this savegame from pilot A to pilot B, others won't be able to restore us. So bail out here if this is the case.
1831 {
1832 return 0;
1833 }
1834 }
1835
1836 // Read description
1837 d_game_unique_state::savegame_description desc;
1838 PHYSFS_read(fp, desc.data(), 20, 1);
1839 desc.back() = 0;
1840
1841 // Skip the current screen shot...
1842 PHYSFS_seek(fp, PHYSFS_tell(fp) + THUMBNAIL_W * THUMBNAIL_H);
1843 #if defined(DXX_BUILD_DESCENT_II)
1844 // And now...skip the goddamn palette stuff that somebody forgot to add
1845 PHYSFS_seek(fp, PHYSFS_tell(fp) + 768);
1846 #endif
1847 // Read the Between levels flag...
1848 PHYSFSX_readSXE32(fp, swap);
1849
1850 // Read the mission info...
1851 savegame_mission_path mission_pathname{};
1852 PHYSFS_read(fp, mission_pathname.original.data(), mission_pathname.original.size(), 1);
1853 mission_name_type name_match_mode;
1854 mission_entry_predicate mission_predicate;
1855 switch (static_cast<savegame_mission_name_abi>(mission_pathname.original.back()))
1856 {
1857 case savegame_mission_name_abi::original: /* Save game without the ability to do extended mission names */
1858 name_match_mode = mission_name_type::basename;
1859 mission_predicate.filesystem_name = mission_pathname.original.data();
1860 #if defined(DXX_BUILD_DESCENT_II)
1861 mission_predicate.check_version = false;
1862 #endif
1863 break;
1864 case savegame_mission_name_abi::pathname: /* Save game with extended mission name */
1865 {
1866 PHYSFS_read(fp, mission_pathname.full.data(), mission_pathname.full.size(), 1);
1867 if (mission_pathname.full.back())
1868 {
1869 struct error_unknown_mission_format : passive_messagebox
1870 {
1871 error_unknown_mission_format() :
1872 passive_messagebox(menu_title{TXT_ERROR}, menu_subtitle{"Unable to load game\nUnrecognized mission name format"}, TXT_OK, grd_curscreen->sc_canvas)
1873 {
1874 }
1875 };
1876 run_blocking_newmenu<error_unknown_mission_format>();
1877 return 0;
1878 }
1879 }
1880 name_match_mode = mission_name_type::pathname;
1881 mission_predicate.filesystem_name = mission_pathname.full.data();
1882 #if defined(DXX_BUILD_DESCENT_II)
1883 mission_predicate.check_version = true;
1884 mission_predicate.descent_version = static_cast<Mission::descent_version_type>(mission_pathname.original[1]);
1885 #endif
1886 break;
1887 default: /* Save game written by a future version of Rebirth. ABI unknown. */
1888 {
1889 struct error_unknown_save_format : passive_messagebox
1890 {
1891 error_unknown_save_format() :
1892 passive_messagebox(menu_title{TXT_ERROR}, menu_subtitle{"Unable to load game\nUnrecognized save game format"}, TXT_OK, grd_curscreen->sc_canvas)
1893 {
1894 }
1895 };
1896 run_blocking_newmenu<error_unknown_save_format>();
1897 }
1898 return 0;
1899 }
1900
1901 if (const auto errstr = load_mission_by_name(mission_predicate, name_match_mode))
1902 {
1903 nm_messagebox(menu_title{TXT_ERROR}, 1, TXT_OK, "Unable to load mission\n'%s'\n\n%s", mission_pathname.full.data(), errstr);
1904 return 0;
1905 }
1906
1907 //Read level info
1908 current_level = PHYSFSX_readSXE32(fp, swap);
1909 PHYSFS_seek(fp, PHYSFS_tell(fp) + sizeof(PHYSFS_sint32)); // skip Next_level_num
1910
1911 //Restore GameTime
1912 tmptime32 = PHYSFSX_readSXE32(fp, swap);
1913 GameTime64 = static_cast<fix64>(tmptime32);
1914
1915 // Start new game....
1916 callsign_t org_callsign;
1917 LevelUniqueObjectState.accumulated_robots = 0;
1918 LevelUniqueObjectState.total_hostages = 0;
1919 GameUniqueState.accumulated_robots = 0;
1920 GameUniqueState.total_hostages = 0;
1921 if (!(Game_mode & GM_MULTI_COOP))
1922 {
1923 Game_mode = GM_NORMAL;
1924 change_playernum_to(0);
1925 N_players = 1;
1926 auto &callsign = vmplayerptr(0u)->callsign;
1927 callsign = InterfaceUniqueState.PilotName;
1928 org_callsign = callsign;
1929 if (secret == secret_restore::none)
1930 {
1931 InitPlayerObject(); //make sure player's object set up
1932 init_player_stats_game(0); //clear all stats
1933 }
1934 }
1935 else // in coop we want to stay the player we are already.
1936 {
1937 org_callsign = get_local_player().callsign;
1938 if (secret == secret_restore::none)
1939 init_player_stats_game(Player_num);
1940 }
1941
1942 if (const auto g = Game_wind)
1943 g->set_visible(0);
1944
1945 //Read player info
1946
1947 {
1948 player_info pl_info;
1949 relocated_player_data rpd;
1950 #if defined(DXX_BUILD_DESCENT_II)
1951 player_info ret_pl_info;
1952 ret_pl_info.mission.hostages_on_board = get_local_plrobj().ctype.player_info.mission.hostages_on_board;
1953 #endif
1954 {
1955 #if DXX_USE_EDITOR
1956 // Don't bother with the other game sequence stuff if loading saved game in editor
1957 if (EditorWindow)
1958 LoadLevel(current_level, 1);
1959 else
1960 #endif
1961 StartNewLevelSub(current_level, 1, secret);
1962
1963 auto &plr = get_local_player();
1964 #if defined(DXX_BUILD_DESCENT_II)
1965 auto &plrobj = get_local_plrobj();
1966 if (secret != secret_restore::none) {
1967 player dummy_player;
1968 state_read_player(fp, dummy_player, swap, pl_info, rpd);
1969 if (secret == secret_restore::survived) { // This means he didn't die, so he keeps what he got in the secret level.
1970 const auto hostages_on_board = ret_pl_info.mission.hostages_on_board;
1971 ret_pl_info = plrobj.ctype.player_info;
1972 ret_pl_info.mission.hostages_on_board = hostages_on_board;
1973 rpd.shields = plrobj.shields;
1974 plr.level = dummy_player.level;
1975 plr.time_level = dummy_player.time_level;
1976
1977 ret_pl_info.homing_object_dist = -1;
1978 plr.hours_level = dummy_player.hours_level;
1979 plr.hours_total = dummy_player.hours_total;
1980 do_cloak_invul_secret_stuff(old_gametime, ret_pl_info);
1981 } else {
1982 plr = dummy_player;
1983 // Keep keys even if they died on secret level (otherwise game becomes impossible)
1984 // Example: Cameron 'Stryker' Fultz's Area 51
1985 pl_info.powerup_flags |= (plrobj.ctype.player_info.powerup_flags &
1986 (PLAYER_FLAGS_BLUE_KEY |
1987 PLAYER_FLAGS_RED_KEY |
1988 PLAYER_FLAGS_GOLD_KEY));
1989 }
1990 } else
1991 #endif
1992 {
1993 state_read_player(fp, plr, swap, pl_info, rpd);
1994 }
1995 LevelUniqueObjectState.accumulated_robots = rpd.num_robots_level;
1996 LevelUniqueObjectState.total_hostages = rpd.hostages_level;
1997 GameUniqueState.accumulated_robots = rpd.num_robots_total;
1998 GameUniqueState.total_hostages = rpd.hostages_total;
1999 }
2000 {
2001 auto &plr = get_local_player();
2002 plr.callsign = org_callsign;
2003 if (Game_mode & GM_MULTI_COOP)
2004 plr.objnum = coop_org_objnum;
2005 }
2006
2007 auto &Primary_weapon = pl_info.Primary_weapon;
2008 // Restore the weapon states
2009 {
2010 int8_t v;
2011 PHYSFS_read(fp, &v, sizeof(int8_t), 1);
2012 Primary_weapon = static_cast<primary_weapon_index_t>(v);
2013 }
2014 auto &Secondary_weapon = pl_info.Secondary_weapon;
2015 {
2016 int8_t v;
2017 PHYSFS_read(fp, &v, sizeof(int8_t), 1);
2018 Secondary_weapon = static_cast<secondary_weapon_index_t>(v);
2019 }
2020
2021 select_primary_weapon(pl_info, nullptr, Primary_weapon, 0);
2022 select_secondary_weapon(pl_info, nullptr, Secondary_weapon, 0);
2023
2024 // Restore the difficulty level
2025 {
2026 const unsigned u = PHYSFSX_readSXE32(fp, swap);
2027 GameUniqueState.Difficulty_level = cast_clamp_difficulty(u);
2028 }
2029
2030 // Restore the cheats enabled flag
2031 game_disable_cheats(); // disable cheats first
2032 cheats.enabled = PHYSFSX_readSXE32(fp, swap);
2033 #if defined(DXX_BUILD_DESCENT_I)
2034 cheats.turbo = PHYSFSX_readSXE32(fp, swap);
2035 #endif
2036
2037 Do_appearance_effect = 0; // Don't do this for middle o' game stuff.
2038
2039 //Clear out all the objects from the lvl file
2040 for (unique_segment &useg : vmsegptr)
2041 useg.objects = object_none;
2042 reset_objects(LevelUniqueObjectState, 1);
2043
2044 //Read objects, and pop 'em into their respective segments.
2045 {
2046 const int i = PHYSFSX_readSXE32(fp, swap);
2047 Objects.set_count(i);
2048 }
2049 range_for (const auto &&objp, vmobjptr)
2050 {
2051 object_rw obj_rw;
2052 PHYSFS_read(fp, &obj_rw, sizeof(obj_rw), 1);
2053 object_rw_swap(&obj_rw, swap);
2054 state_object_rw_to_object(&obj_rw, objp);
2055 }
2056
2057 range_for (const auto &&obj, vmobjptridx)
2058 {
2059 obj->rtype.pobj_info.alt_textures = -1;
2060 if ( obj->type != OBJ_NONE ) {
2061 const auto segnum = obj->segnum;
2062 obj_link_unchecked(Objects.vmptr, obj, Segments.vmptridx(segnum));
2063 }
2064 #if defined(DXX_BUILD_DESCENT_II)
2065 //look for, and fix, boss with bogus shields
2066 if (obj->type == OBJ_ROBOT && Robot_info[get_robot_id(obj)].boss_flag) {
2067 fix save_shields = obj->shields;
2068
2069 copy_defaults_to_robot(obj); //calculate starting shields
2070
2071 //if in valid range, use loaded shield value
2072 if (save_shields > 0 && save_shields <= obj->shields)
2073 obj->shields = save_shields;
2074 else
2075 obj->shields /= 2; //give player a break
2076 }
2077 #endif
2078 }
2079 special_reset_objects(LevelUniqueObjectState);
2080 /* Reload plrobj reference. The player's object number may have
2081 * been changed by the state_object_rw_to_object call.
2082 */
2083 auto &plrobj = get_local_plrobj();
2084 plrobj.shields = rpd.shields;
2085 #if defined(DXX_BUILD_DESCENT_II)
2086 if (secret == secret_restore::survived)
2087 { // This means he didn't die, so he keeps what he got in the secret level.
2088 ret_pl_info.mission.last_score = pl_info.mission.last_score;
2089 plrobj.ctype.player_info = ret_pl_info;
2090 }
2091 else
2092 #endif
2093 plrobj.ctype.player_info = pl_info;
2094 }
2095 /* Reload plrobj reference. This is unnecessary for correctness,
2096 * but is required by scoping rules, since the previous correct copy
2097 * goes out of scope when pl_info goes out of scope, and that needs
2098 * to be removed from scope to avoid a shadow warning when
2099 * cooperative players are loaded below.
2100 */
2101 auto &plrobj = get_local_plrobj();
2102
2103 // 1 = Didn't die on secret level.
2104 // 2 = Died on secret level.
2105 if (secret != secret_restore::none && (Current_level_num >= 0)) {
2106 set_pos_from_return_segment();
2107 #if defined(DXX_BUILD_DESCENT_II)
2108 if (secret == secret_restore::died)
2109 init_player_stats_new_ship(Player_num);
2110 #endif
2111 }
2112
2113 //Restore wall info
2114 init_exploding_walls();
2115 {
2116 auto &Walls = LevelUniqueWallSubsystemState.Walls;
2117 Walls.set_count(PHYSFSX_readSXE32(fp, swap));
2118 range_for (const auto &&w, Walls.vmptr)
2119 wall_read(fp, *w);
2120
2121 #if defined(DXX_BUILD_DESCENT_II)
2122 //now that we have the walls, check if any sounds are linked to
2123 //walls that are now open
2124 range_for (const auto &&wp, Walls.vcptr)
2125 {
2126 auto &w = *wp;
2127 if (w.type == WALL_OPEN)
2128 digi_kill_sound_linked_to_segment(w.segnum,w.sidenum,-1); //-1 means kill any sound
2129 }
2130
2131 //Restore exploding wall info
2132 if (version >= 10) {
2133 unsigned i = PHYSFSX_readSXE32(fp, swap);
2134 expl_wall_read_n_swap(Walls.vmptr, fp, swap, i);
2135 }
2136 #endif
2137 }
2138
2139 //Restore door info
2140 {
2141 auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
2142 ActiveDoors.set_count(PHYSFSX_readSXE32(fp, swap));
2143 range_for (auto &&ad, ActiveDoors.vmptr)
2144 active_door_read(fp, ad);
2145 }
2146
2147 #if defined(DXX_BUILD_DESCENT_II)
2148 if (version >= 14) { //Restore cloaking wall info
2149 unsigned num_cloaking_walls = PHYSFSX_readSXE32(fp, swap);
2150 auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls;
2151 CloakingWalls.set_count(num_cloaking_walls);
2152 range_for (auto &&w, CloakingWalls.vmptr)
2153 cloaking_wall_read(w, fp);
2154 }
2155 #endif
2156
2157 //Restore trigger info
2158 {
2159 auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
2160 Triggers.set_count(PHYSFSX_readSXE32(fp, swap));
2161 range_for (trigger &t, Triggers.vmptr)
2162 trigger_read(fp, t);
2163 }
2164
2165 //Restore tmap info (to temp values so we can use compiled-in tmap info to compute static_light
2166 range_for (const auto &&segp, vmsegptridx)
2167 {
2168 range_for (const unsigned j, xrange(6u))
2169 {
2170 const uint16_t wall_num = PHYSFSX_readSXE16(fp, swap);
2171 segp->shared_segment::sides[j].wall_num = wallnum_t{wall_num};
2172 TempTmapNum[segp][j] = static_cast<texture1_value>(PHYSFSX_readSXE16(fp, swap));
2173 TempTmapNum2[segp][j] = static_cast<texture2_value>(PHYSFSX_readSXE16(fp, swap));
2174 }
2175 }
2176
2177 //Restore the fuelcen info
2178 LevelUniqueControlCenterState.Control_center_destroyed = PHYSFSX_readSXE32(fp, swap);
2179 #if defined(DXX_BUILD_DESCENT_I)
2180 LevelUniqueControlCenterState.Countdown_seconds_left = PHYSFSX_readSXE32(fp, swap);
2181 LevelUniqueControlCenterState.Countdown_timer = 0;
2182 #elif defined(DXX_BUILD_DESCENT_II)
2183 LevelUniqueControlCenterState.Countdown_timer = PHYSFSX_readSXE32(fp, swap);
2184 #endif
2185 const unsigned Num_robot_centers = PHYSFSX_readSXE32(fp, swap);
2186 LevelSharedRobotcenterState.Num_robot_centers = Num_robot_centers;
2187 range_for (auto &r, partial_range(RobotCenters, Num_robot_centers))
2188 #if defined(DXX_BUILD_DESCENT_I)
2189 matcen_info_read(fp, r, STATE_MATCEN_VERSION);
2190 #elif defined(DXX_BUILD_DESCENT_II)
2191 matcen_info_read(fp, r);
2192 #endif
2193 control_center_triggers_read(&ControlCenterTriggers, fp);
2194 const unsigned Num_fuelcenters = PHYSFSX_readSXE32(fp, swap);
2195 LevelUniqueFuelcenterState.Num_fuelcenters = Num_fuelcenters;
2196 range_for (auto &s, partial_range(Station, Num_fuelcenters))
2197 {
2198 fuelcen_read(fp, s);
2199 #if defined(DXX_BUILD_DESCENT_I)
2200 // NOTE: Usually Descent1 handles countdown by Timer value of the Reactor Station. Since we now use Descent2 code to handle countdown (which we do in case there IS NO Reactor Station which causes potential trouble in Multiplayer), let's find the Reactor here and read the timer from it.
2201 if (s.Type == segment_special::controlcen)
2202 LevelUniqueControlCenterState.Countdown_timer = s.Timer;
2203 #endif
2204 }
2205
2206 // Restore the control cen info
2207 LevelUniqueControlCenterState.Control_center_been_hit = PHYSFSX_readSXE32(fp, swap);
2208 {
2209 const int cc = PHYSFSX_readSXE32(fp, swap);
2210 LevelUniqueControlCenterState.Control_center_player_been_seen = static_cast<player_visibility_state>(cc);
2211 }
2212 LevelUniqueControlCenterState.Frametime_until_next_fire = PHYSFSX_readSXE32(fp, swap);
2213 LevelUniqueControlCenterState.Control_center_present = PHYSFSX_readSXE32(fp, swap);
2214 LevelUniqueControlCenterState.Dead_controlcen_object_num = PHYSFSX_readSXE32(fp, swap);
2215 if (LevelUniqueControlCenterState.Control_center_destroyed)
2216 LevelUniqueControlCenterState.Total_countdown_time = LevelUniqueControlCenterState.Countdown_timer / F0_5; // we do not need to know this, but it should not be 0 either...
2217
2218 // Restore the AI state
2219 ai_restore_state( fp, version, swap );
2220
2221 {
2222 auto &Automap_visited = LevelUniqueAutomapState.Automap_visited;
2223 // Restore the automap visited info
2224 Automap_visited = {};
2225 DXX_MAKE_MEM_UNDEFINED(Automap_visited.begin(), Automap_visited.end());
2226 PHYSFS_read(fp, Automap_visited.data(), sizeof(uint8_t), std::max<std::size_t>(Highest_segment_index + 1, MAX_SEGMENTS_ORIGINAL));
2227 }
2228
2229 {
2230 // Restore hacked up weapon system stuff.
2231 auto &player_info = plrobj.ctype.player_info;
2232 /* These values were never saved, so coerce them to a sane default.
2233 */
2234 player_info.Fusion_charge = 0;
2235 player_info.Player_eggs_dropped = false;
2236 player_info.FakingInvul = false;
2237 player_info.lavafall_hiss_playing = false;
2238 player_info.missile_gun = 0;
2239 player_info.Spreadfire_toggle = 0;
2240 #if defined(DXX_BUILD_DESCENT_II)
2241 player_info.Helix_orientation = 0;
2242 #endif
2243 player_info.Last_bumped_local_player = 0;
2244 auto &Next_laser_fire_time = player_info.Next_laser_fire_time;
2245 auto &Next_missile_fire_time = player_info.Next_missile_fire_time;
2246 player_info.Auto_fire_fusion_cannon_time = 0;
2247 player_info.Next_flare_fire_time = GameTime64;
2248 Next_laser_fire_time = GameTime64;
2249 Next_missile_fire_time = GameTime64;
2250
2251 state_game_id = 0;
2252
2253 if ( version >= 7 ) {
2254 state_game_id = PHYSFSX_readSXE32(fp, swap);
2255 cheats.rapidfire = PHYSFSX_readSXE32(fp, swap);
2256 PHYSFS_seek(fp, PHYSFS_tell(fp) + sizeof(PHYSFS_sint32)); // PHYSFSX_readSXE32(fp, swap); // was Lunacy
2257 PHYSFS_seek(fp, PHYSFS_tell(fp) + sizeof(PHYSFS_sint32)); // PHYSFSX_readSXE32(fp, swap); // was Lunacy, too... and one was Ugly robot stuff a long time ago...
2258 #if defined(DXX_BUILD_DESCENT_I)
2259 cheats.ghostphysics = PHYSFSX_readSXE32(fp, swap);
2260 PHYSFS_seek(fp, PHYSFS_tell(fp) + sizeof(PHYSFS_sint32)); // PHYSFSX_readSXE32(fp, swap);
2261 #endif
2262 }
2263
2264 #if defined(DXX_BUILD_DESCENT_II)
2265 if (version >= 17) {
2266 range_for (auto &i, MarkerState.imobjidx)
2267 i = PHYSFSX_readSXE32(fp, swap);
2268 PHYSFS_seek(fp, PHYSFS_tell(fp) + (NUM_MARKERS)*(CALLSIGN_LEN+1)); // PHYSFS_read(fp, MarkerOwner, sizeof(MarkerOwner), 1); // skip obsolete MarkerOwner
2269 range_for (auto &i, MarkerState.message)
2270 {
2271 std::array<char, MARKER_MESSAGE_LEN> a;
2272 PHYSFS_read(fp, a.data(), a.size(), 1);
2273 i.copy_if(a);
2274 }
2275 }
2276 else {
2277 int num;
2278
2279 // skip dummy info
2280
2281 num = PHYSFSX_readSXE32(fp, swap); // was NumOfMarkers
2282 PHYSFS_seek(fp, PHYSFS_tell(fp) + sizeof(PHYSFS_sint32)); // PHYSFSX_readSXE32(fp, swap); // was CurMarker
2283
2284 PHYSFS_seek(fp, PHYSFS_tell(fp) + num * (sizeof(vms_vector) + 40));
2285
2286 range_for (auto &i, MarkerState.imobjidx)
2287 i = object_none;
2288 }
2289
2290 if (version>=11) {
2291 if (secret != secret_restore::survived)
2292 Afterburner_charge = PHYSFSX_readSXE32(fp, swap);
2293 else {
2294 PHYSFSX_readSXE32(fp, swap);
2295 }
2296 }
2297 if (version>=12) {
2298 //read last was super information
2299 auto &Primary_last_was_super = player_info.Primary_last_was_super;
2300 std::array<uint8_t, MAX_PRIMARY_WEAPONS> last_was_super;
2301 /* Descent 2 shipped with Primary_last_was_super and
2302 * Secondary_last_was_super each sized to contain MAX_*_WEAPONS,
2303 * but only the first half of those are ever used.
2304 * Unfortunately, the save file format is defined as saving
2305 * MAX_*_WEAPONS for each. Read into a temporary, then copy the
2306 * meaningful elements to the live data.
2307 */
2308 PHYSFS_read(fp, &last_was_super, MAX_PRIMARY_WEAPONS, 1);
2309 Primary_last_was_super = 0;
2310 for (uint_fast32_t j = primary_weapon_index_t::VULCAN_INDEX; j != primary_weapon_index_t::SUPER_LASER_INDEX; ++j)
2311 {
2312 if (last_was_super[j])
2313 Primary_last_was_super |= 1 << j;
2314 }
2315 PHYSFS_read(fp, &last_was_super, MAX_SECONDARY_WEAPONS, 1);
2316 auto &Secondary_last_was_super = player_info.Secondary_last_was_super;
2317 Secondary_last_was_super = 0;
2318 for (uint_fast32_t j = secondary_weapon_index_t::CONCUSSION_INDEX; j != secondary_weapon_index_t::SMISSILE1_INDEX; ++j)
2319 {
2320 if (last_was_super[j])
2321 Secondary_last_was_super |= 1 << j;
2322 }
2323 }
2324
2325 if (version >= 12) {
2326 Flash_effect = PHYSFSX_readSXE32(fp, swap);
2327 tmptime32 = PHYSFSX_readSXE32(fp, swap);
2328 Time_flash_last_played = static_cast<fix64>(tmptime32);
2329 PaletteRedAdd = PHYSFSX_readSXE32(fp, swap);
2330 PaletteGreenAdd = PHYSFSX_readSXE32(fp, swap);
2331 PaletteBlueAdd = PHYSFSX_readSXE32(fp, swap);
2332 } else {
2333 Flash_effect = 0;
2334 Time_flash_last_played = 0;
2335 PaletteRedAdd = 0;
2336 PaletteGreenAdd = 0;
2337 PaletteBlueAdd = 0;
2338 }
2339
2340 // Load Light_subtracted
2341 if (version >= 16) {
2342 if ( Highest_segment_index+1 > MAX_SEGMENTS_ORIGINAL )
2343 {
2344 for (unique_segment &useg : vmsegptr)
2345 {
2346 PHYSFS_read(fp, &useg.light_subtracted, sizeof(useg.light_subtracted), 1);
2347 }
2348 }
2349 else
2350 {
2351 range_for (unique_segment &i, partial_range(Segments, MAX_SEGMENTS_ORIGINAL))
2352 {
2353 PHYSFS_read(fp, &i.light_subtracted, sizeof(i.light_subtracted), 1);
2354 }
2355 }
2356 apply_all_changed_light(LevelSharedDestructibleLightState, Segments.vmptridx);
2357 } else {
2358 for (unique_segment &useg : vmsegptr)
2359 useg.light_subtracted = 0;
2360 }
2361
2362 if (secret == secret_restore::none)
2363 {
2364 if (version >= 20) {
2365 First_secret_visit = PHYSFSX_readSXE32(fp, swap);
2366 } else
2367 First_secret_visit = 1;
2368 } else
2369 First_secret_visit = 0;
2370
2371 if (secret != secret_restore::survived)
2372 {
2373 player_info.Omega_charge = 0;
2374 /* The savegame does not record this, so pick a value. Be
2375 * nice to the player: let the cannon recharge immediately.
2376 */
2377 player_info.Omega_recharge_delay = 0;
2378 }
2379 if (version >= 22)
2380 {
2381 auto i = PHYSFSX_readSXE32(fp, swap);
2382 if (secret != secret_restore::survived)
2383 {
2384 player_info.Omega_charge = i;
2385 }
2386 }
2387 #endif
2388 }
2389
2390 // static_light should now be computed - now actually set tmap info
2391 range_for (const auto &&segp, vmsegptridx)
2392 {
2393 range_for (const unsigned j, xrange(6u))
2394 {
2395 auto &uside = segp->unique_segment::sides[j];
2396 uside.tmap_num = TempTmapNum[segp][j];
2397 uside.tmap_num2 = TempTmapNum2[segp][j];
2398 }
2399 }
2400
2401 // Read Coop Info
2402 if (Game_mode & GM_MULTI_COOP)
2403 {
2404 player restore_players[MAX_PLAYERS];
2405 object restore_objects[MAX_PLAYERS];
2406 int coop_got_nplayers = 0;
2407
2408 for (playernum_t i = 0; i < MAX_PLAYERS; i++)
2409 {
2410 // prepare arrays for mapping our players below
2411 coop_player_got[i] = 0;
2412
2413 // read the stored players
2414 player_info pl_info;
2415 relocated_player_data rpd;
2416 /* No need to reload num_robots_level again. It was already
2417 * restored above when the local player was restored.
2418 */
2419 state_read_player(fp, restore_players[i], swap, pl_info, rpd);
2420
2421 // make all (previous) player objects to ghosts but store them first for later remapping
2422 const auto &&obj = vmobjptr(restore_players[i].objnum);
2423 if (restore_players[i].connected == CONNECT_PLAYING && obj->type == OBJ_PLAYER)
2424 {
2425 obj->ctype.player_info = pl_info;
2426 obj->shields = rpd.shields;
2427 restore_objects[i] = *obj;
2428 obj->type = OBJ_GHOST;
2429 multi_reset_player_object(obj);
2430 }
2431 }
2432 for (playernum_t i = 0; i < MAX_PLAYERS; i++) // copy restored players to the current slots
2433 {
2434 for (unsigned j = 0; j < MAX_PLAYERS; j++)
2435 {
2436 // map stored players to current players depending on their unique (which we made sure) callsign
2437 if (vcplayerptr(i)->connected == CONNECT_PLAYING && restore_players[j].connected == CONNECT_PLAYING && vcplayerptr(i)->callsign == restore_players[j].callsign)
2438 {
2439 auto &p = *vmplayerptr(i);
2440 const auto sav_objnum = p.objnum;
2441 p = restore_players[j];
2442 p.objnum = sav_objnum;
2443 coop_player_got[i] = 1;
2444 coop_got_nplayers++;
2445
2446 const auto &&obj = vmobjptridx(vcplayerptr(i)->objnum);
2447 // since a player always uses the same object, we just have to copy the saved object properties to the existing one. i hate you...
2448 obj->control_source = restore_objects[j].control_source;
2449 obj->movement_source = restore_objects[j].movement_source;
2450 obj->render_type = restore_objects[j].render_type;
2451 obj->flags = restore_objects[j].flags;
2452 obj->pos = restore_objects[j].pos;
2453 obj->orient = restore_objects[j].orient;
2454 obj->size = restore_objects[j].size;
2455 obj->shields = restore_objects[j].shields;
2456 obj->lifeleft = restore_objects[j].lifeleft;
2457 obj->mtype.phys_info = restore_objects[j].mtype.phys_info;
2458 obj->rtype.pobj_info = restore_objects[j].rtype.pobj_info;
2459 // make this restored player object an actual player again
2460 obj->type = OBJ_PLAYER;
2461 set_player_id(obj, i); // assign player object id to player number
2462 multi_reset_player_object(obj);
2463 update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, obj);
2464 }
2465 }
2466 }
2467 {
2468 std::array<char, MISSION_NAME_LEN + 1> a;
2469 PHYSFS_read(fp, a.data(), a.size(), 1);
2470 Netgame.mission_title.copy_if(a);
2471 }
2472 {
2473 std::array<char, 9> a;
2474 PHYSFS_read(fp, a.data(), a.size(), 1);
2475 Netgame.mission_name.copy_if(a);
2476 }
2477 Netgame.levelnum = PHYSFSX_readSXE32(fp, swap);
2478 PHYSFS_read(fp, &Netgame.difficulty, sizeof(ubyte), 1);
2479 PHYSFS_read(fp, &Netgame.game_status, sizeof(ubyte), 1);
2480 PHYSFS_read(fp, &Netgame.numplayers, sizeof(ubyte), 1);
2481 PHYSFS_read(fp, &Netgame.max_numplayers, sizeof(ubyte), 1);
2482 PHYSFS_read(fp, &Netgame.numconnected, sizeof(ubyte), 1);
2483 Netgame.level_time = PHYSFSX_readSXE32(fp, swap);
2484 for (playernum_t i = 0; i < MAX_PLAYERS; i++)
2485 {
2486 const auto &&objp = vmobjptr(vcplayerptr(i)->objnum);
2487 auto &pi = objp->ctype.player_info;
2488 Netgame.killed[i] = pi.net_killed_total;
2489 Netgame.player_score[i] = pi.mission.score;
2490 Netgame.net_player_flags[i] = pi.powerup_flags;
2491 }
2492 for (playernum_t i = 0; i < MAX_PLAYERS; i++) // Disconnect connected players not available in this Savegame
2493 if (!coop_player_got[i] && vcplayerptr(i)->connected == CONNECT_PLAYING)
2494 multi_disconnect_player(i);
2495 Viewer = ConsoleObject = &get_local_plrobj(); // make sure Viewer and ConsoleObject are set up (which we skipped by not using InitPlayerObject but we need since objects changed while loading)
2496 special_reset_objects(LevelUniqueObjectState); // since we juggled around with objects to remap coop players rebuild the index of free objects
2497 state_set_next_autosave(GameUniqueState, Netgame.MPGameplayOptions.AutosaveInterval);
2498 }
2499 else
2500 state_set_next_autosave(GameUniqueState, PlayerCfg.SPGameplayOptions.AutosaveInterval);
2501 if (const auto g = Game_wind)
2502 if (!g->is_visible())
2503 g->set_visible(1);
2504 reset_time();
2505
2506 return 1;
2507 }
2508
deny_save_game(fvcobjptr & vcobjptr,const d_level_unique_control_center_state & LevelUniqueControlCenterState)2509 deny_save_result deny_save_game(fvcobjptr &vcobjptr, const d_level_unique_control_center_state &LevelUniqueControlCenterState)
2510 {
2511 if (LevelUniqueControlCenterState.Control_center_destroyed)
2512 return deny_save_result::denied;
2513 if (Game_mode & GM_MULTI)
2514 {
2515 if (!(Game_mode & GM_MULTI_COOP))
2516 /* Deathmatch games can never be saved */
2517 return deny_save_result::denied;
2518 if (multi_common_deny_save_game(vcobjptr, partial_const_range(Players, N_players)))
2519 return deny_save_result::denied;
2520 }
2521 return deny_save_result::allowed;
2522 }
2523
2524 }
2525
2526 namespace dcx {
2527
state_get_game_id(const d_game_unique_state::savegame_file_path & filename)2528 int state_get_game_id(const d_game_unique_state::savegame_file_path &filename)
2529 {
2530 int version;
2531 int swap = 0; // if file is not endian native, have to swap all shorts and ints
2532 char id[5];
2533 callsign_t saved_callsign;
2534
2535 #ifndef NDEBUG
2536 if (CGameArg.SysUsePlayersDir && strncmp(filename.data(), PLAYER_DIRECTORY_TEXT, sizeof(PLAYER_DIRECTORY_TEXT) - 1))
2537 Int3();
2538 #endif
2539
2540 if (!(Game_mode & GM_MULTI_COOP))
2541 return 0;
2542
2543 auto fp = PHYSFSX_openReadBuffered(filename.data()).first;
2544 if ( !fp ) return 0;
2545
2546 //Read id
2547 PHYSFS_read(fp, id, sizeof(char) * 4, 1);
2548 if ( memcmp( id, dgss_id, 4 )) {
2549 return 0;
2550 }
2551
2552 //Read version
2553 //Check for swapped file here, as dgss_id is written as a string (i.e. endian independent)
2554 PHYSFS_read(fp, &version, sizeof(int), 1);
2555 if (version & 0xffff0000)
2556 {
2557 swap = 1;
2558 version = SWAPINT(version);
2559 }
2560
2561 if (version < STATE_COMPATIBLE_VERSION) {
2562 return 0;
2563 }
2564
2565 // Read Coop state_game_id to validate the savegame we are about to load matches the others
2566 state_game_id = PHYSFSX_readSXE32(fp, swap);
2567 PHYSFS_read(fp, &saved_callsign, sizeof(char)*CALLSIGN_LEN+1, 1);
2568 if (!(saved_callsign == get_local_player().callsign)) // check the callsign of the palyer who saved this state. It MUST match. If we transferred this savegame from pilot A to pilot B, others won't be able to restore us. So bail out here if this is the case.
2569 return 0;
2570
2571 return state_game_id;
2572 }
2573
2574 }
2575