Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  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. }
  2407.