Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

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