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