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 load & save player's settings (*.plr file)
  23.  *
  24.  */
  25.  
  26. #include <stdexcept>
  27. #include <stdio.h>
  28. #include <string.h>
  29. #if !defined(_MSC_VER) && !defined(macintosh)
  30. #include <unistd.h>
  31. #endif
  32. #include <errno.h>
  33. #include <ctype.h>
  34. #include <inttypes.h>
  35.  
  36. #include "dxxerror.h"
  37. #include "strutil.h"
  38. #include "game.h"
  39. #include "gameseq.h"
  40. #include "player.h"
  41. #include "playsave.h"
  42. #include "joy.h"
  43. #include "digi.h"
  44. #include "newmenu.h"
  45. #include "palette.h"
  46. #include "menu.h"
  47. #include "config.h"
  48. #include "text.h"
  49. #include "state.h"
  50. #include "gauges.h"
  51. #include "screens.h"
  52. #include "powerup.h"
  53. #include "makesig.h"
  54. #include "u_mem.h"
  55. #include "args.h"
  56. #include "vers_id.h"
  57. #include "newdemo.h"
  58. #include "gauges.h"
  59. #include "nvparse.h"
  60.  
  61. #include "compiler-range_for.h"
  62. #include "d_range.h"
  63. #include "partial_range.h"
  64.  
  65. #define PLAYER_EFFECTIVENESS_FILENAME_FORMAT    PLAYER_DIRECTORY_STRING("%s.eff")
  66.  
  67. #define GameNameStr "game_name"
  68. #define GameModeStr "gamemode"
  69. #define RefusePlayersStr "RefusePlayers"
  70. #define DifficultyStr "difficulty"
  71. #define GameFlagsStr "game_flags"
  72. #define AllowedItemsStr "AllowedItems"
  73. #define SpawnGrantedItemsStr "SpawnGrantedItems"
  74. #define DuplicatePrimariesStr "DuplicatePrimaries"
  75. #define DuplicateSecondariesStr "DuplicateSecondaries"
  76. #define DuplicateAccessoriesStr "DuplicateAccessories"
  77. #define ShufflePowerupsStr "ShufflePowerups"
  78. #define AlwaysLightingStr "AlwaysLighting"
  79. #define ShowEnemyNamesStr "ShowEnemyNames"
  80. #define BrightPlayersStr "BrightPlayers"
  81. #define InvulAppearStr "InvulAppear"
  82. #define KillGoalStr "KillGoal"
  83. #define PlayTimeAllowedStr "PlayTimeAllowed"
  84. #define ControlInvulTimeStr "control_invul_time"
  85. #define PacketsPerSecStr "PacketsPerSec"
  86. #define NoFriendlyFireStr "NoFriendlyFire"
  87. #define MouselookFlagsStr "Mouselook"
  88. #define AutosaveIntervalStr     "AutosaveInterval"
  89. #define TrackerStr "Tracker"
  90. #define TrackerNATHPStr "trackernat"
  91. #define NGPVersionStr "ngp version"
  92.  
  93. #if defined(DXX_BUILD_DESCENT_I)
  94. #define PLX_OPTION_HEADER_TEXT  "[D1X Options]"
  95. #define WEAPON_REORDER_HEADER_TEXT "[weapon reorder]"
  96. #define WEAPON_REORDER_PRIMARY_NAME_TEXT        "primary"
  97. #define WEAPON_REORDER_PRIMARY_VALUE_TEXT       "0x%x,0x%x,0x%x,0x%x,0x%x,0x%x"
  98. #define WEAPON_REORDER_SECONDARY_NAME_TEXT      "secondary"
  99. #define WEAPON_REORDER_SECONDARY_VALUE_TEXT     "0x%x,0x%x,0x%x,0x%x,0x%x,0x%x"
  100. //version 5  ->  6: added new highest level information
  101. //version 6  ->  7: stripped out the old saved_game array.
  102. //version 7 -> 8: readded the old saved_game array since this is needed
  103. //                for shareware saved games
  104. //the shareware is level 4
  105.  
  106. #define SAVED_GAME_VERSION 8 //increment this every time saved_game struct changes
  107. #define COMPATIBLE_SAVED_GAME_VERSION 4
  108. #define COMPATIBLE_PLAYER_STRUCT_VERSION 16
  109. #elif defined(DXX_BUILD_DESCENT_II)
  110. #define PLX_OPTION_HEADER_TEXT  "[D2X OPTIONS]"
  111. //version 5  ->  6: added new highest level information
  112. //version 6  ->  7: stripped out the old saved_game array.
  113. //version 7  ->  8: added reticle flag, & window size
  114. //version 8  ->  9: removed player_struct_version
  115. //version 9  -> 10: added default display mode
  116. //version 10 -> 11: added all toggles in toggle menu
  117. //version 11 -> 12: added weapon ordering
  118. //version 12 -> 13: added more keys
  119. //version 13 -> 14: took out marker key
  120. //version 14 -> 15: added guided in big window
  121. //version 15 -> 16: added small windows in cockpit
  122. //version 16 -> 17: ??
  123. //version 17 -> 18: save guidebot name
  124. //version 18 -> 19: added automap-highres flag
  125. //version 19 -> 20: added kconfig data for windows joysticks
  126. //version 20 -> 21: save seperate config types for DOS & Windows
  127. //version 21 -> 22: save lifetime netstats
  128. //version 22 -> 23: ??
  129. //version 23 -> 24: add name of joystick for windows version.
  130.  
  131. #define PLAYER_FILE_VERSION 24 //increment this every time the player file changes
  132. #define COMPATIBLE_PLAYER_FILE_VERSION 17
  133. #define AllowMarkerViewStr "Allow_marker_view"
  134. #define ThiefAbsenceFlagStr     "ThiefAbsent"
  135. #define ThiefNoEnergyWeaponsFlagStr     "ThiefNoEnergyWeapons"
  136. #define AllowGuidebotStr "AllowGuidebot"
  137. #endif
  138. #define KEYBOARD_HEADER_TEXT    "[keyboard]"
  139. #define SENSITIVITY_NAME_TEXT   "sensitivity"
  140. #define SENSITIVITY_VALUE_TEXT  "%d"
  141. #define LINEAR_NAME_TEXT        "linearity"
  142. #define LINEAR_VALUE_TEXT       "%d"
  143. #define SPEED_NAME_TEXT         "speed"
  144. #define SPEED_VALUE_TEXT        "%d"
  145. #define DEADZONE_NAME_TEXT      "deadzone"
  146. #define DEADZONE_VALUE_TEXT     "%d"
  147. #define JOYSTICK_HEADER_TEXT    "[joystick]"
  148. #define MOUSE_HEADER_TEXT       "[mouse]"
  149. #define MOUSE_FLIGHTSIM_NAME_TEXT       "flightsim"
  150. #define MOUSE_FLIGHTSIM_VALUE_TEXT      "%d"
  151. #define MOUSE_FSDEAD_NAME_TEXT  "fsdead"
  152. #define MOUSE_FSDEAD_VALUE_TEXT "%d"
  153. #define MOUSE_FSINDICATOR_NAME_TEXT     "fsindi"
  154. #define MOUSE_FSINDICATOR_VALUE_TEXT    "%d"
  155. #define MOUSE_OVERRUN_NAME_TEXT "overrun"
  156. #define WEAPON_KEYv2_HEADER_TEXT        "[weapon keys v2]"
  157. #define WEAPON_KEYv2_VALUE_TEXT "0x%x,0x%x,0x%x"
  158. #define COCKPIT_HEADER_TEXT "[cockpit]"
  159. #define COCKPIT_MODE_NAME_TEXT "mode"
  160. #define COCKPIT_HUD_NAME_TEXT "hud"
  161. #define COCKPIT_RETICLE_TYPE_NAME_TEXT "rettype"
  162. #define COCKPIT_RETICLE_COLOR_NAME_TEXT "retrgba"
  163. #define COCKPIT_RETICLE_SIZE_NAME_TEXT "retsize"
  164. #define TOGGLES_HEADER_TEXT "[toggles]"
  165. #define TOGGLES_BOMBGAUGE_NAME_TEXT "bombgauge"
  166. #define TOGGLES_ESCORTHOTKEYS_NAME_TEXT "escorthotkeys"
  167. #define TOGGLES_PERSISTENTDEBRIS_NAME_TEXT "persistentdebris"
  168. #define TOGGLES_PRSHOT_NAME_TEXT "prshot"
  169. #define TOGGLES_NOREDUNDANCY_NAME_TEXT "noredundancy"
  170. #define TOGGLES_MULTIMESSAGES_NAME_TEXT "multimessages"
  171. #define TOGGLES_MULTIPINGHUD_NAME_TEXT "multipinghud"
  172. #define TOGGLES_NORANKINGS_NAME_TEXT "norankings"
  173. #define TOGGLES_AUTOMAPFREEFLIGHT_NAME_TEXT "automapfreeflight"
  174. #define TOGGLES_NOFIREAUTOSELECT_NAME_TEXT "nofireautoselect"
  175. #define TOGGLES_CYCLEAUTOSELECTONLY_NAME_TEXT "cycleautoselectonly"
  176. #define TOGGLES_FRIENDMISSILEVIEW_NAME_TEXT "friendmissileview"
  177. #define TOGGLES_CLOAKINVULTIMER_NAME_TEXT "cloakinvultimer"
  178. #define TOGGLES_RESPAWN_ANY_KEY "respawnkey"
  179. #define TOGGLES_MOUSELOOK       "mouselook"
  180. #define TOGGLES_THIEF_ABSENCE_SP        "thiefabsent"
  181. #define TOGGLES_THIEF_NO_ENERGY_WEAPONS_SP      "thiefnoenergyweapons"
  182. #define TOGGLES_AUTOSAVE_INTERVAL_SP    "autosaveinterval"
  183. #define GRAPHICS_HEADER_TEXT "[graphics]"
  184. #define GRAPHICS_ALPHAEFFECTS_NAME_TEXT "alphaeffects"
  185. #define GRAPHICS_DYNLIGHTCOLOR_NAME_TEXT "dynlightcolor"
  186. #define PLX_VERSION_HEADER_TEXT "[plx version]"
  187. #define END_TEXT        "[end]"
  188.  
  189. #define SAVE_FILE_ID MAKE_SIG('D','P','L','R')
  190.  
  191. struct player_config PlayerCfg;
  192. namespace dsx {
  193. #if defined(DXX_BUILD_DESCENT_I)
  194. static void plyr_read_stats();
  195. static std::array<saved_game_sw, N_SAVE_SLOTS> saved_games;
  196. #elif defined(DXX_BUILD_DESCENT_II)
  197. static inline void plyr_read_stats() {}
  198. static int get_lifetime_checksum (int a,int b);
  199. #endif
  200. }
  201.  
  202. template <std::size_t N>
  203. static void check_weapon_reorder(std::array<ubyte, N> &w)
  204. {
  205.         uint_fast32_t m = 0;
  206.         range_for (const auto i, w)
  207.                 if (i == 255)
  208.                         m |= 1 << N;
  209.                 else if (i < N - 1)
  210.                         m |= 1 << i;
  211.                 else
  212.                         break;
  213.         if (m != ((1 << N) | ((1 << (N - 1)) - 1)))
  214.         {
  215.                 w[0] = 255;
  216.                 range_for (const uint_fast32_t i, xrange(1u, N))
  217.                         w[i] = i - 1;
  218.         }
  219. }
  220.  
  221. namespace dsx {
  222. int new_player_config()
  223. {
  224. #if defined(DXX_BUILD_DESCENT_I)
  225.         range_for (auto &i, saved_games)
  226.                 i.name[0] = 0;
  227. #endif
  228.         InitWeaponOrdering (); //setup default weapon priorities
  229.         PlayerCfg.ControlType=0; // Assume keyboard
  230.         PlayerCfg.RespawnMode = RespawnPress::Any;
  231.         PlayerCfg.MouselookFlags = 0;
  232.         PlayerCfg.KeySettings = DefaultKeySettings;
  233.         PlayerCfg.KeySettingsRebirth = DefaultKeySettingsRebirth;
  234.         kc_set_controls();
  235.  
  236.         PlayerCfg.DefaultDifficulty = DEFAULT_DIFFICULTY;
  237.         PlayerCfg.AutoLeveling = 1;
  238.         PlayerCfg.NHighestLevels = 1;
  239.         PlayerCfg.HighestLevels[0].Shortname[0] = 0; //no name for mission 0
  240.         PlayerCfg.HighestLevels[0].LevelNum = 1; //was highest level in old struct
  241.         PlayerCfg.KeyboardSens[0] = PlayerCfg.KeyboardSens[1] = PlayerCfg.KeyboardSens[2] = PlayerCfg.KeyboardSens[3] = PlayerCfg.KeyboardSens[4] = 16;
  242.         PlayerCfg.JoystickSens[0] = PlayerCfg.JoystickSens[1] = PlayerCfg.JoystickSens[2] = PlayerCfg.JoystickSens[3] = PlayerCfg.JoystickSens[4] = PlayerCfg.JoystickSens[5] = 8;
  243.         PlayerCfg.JoystickDead[0] = PlayerCfg.JoystickDead[1] = PlayerCfg.JoystickDead[2] = PlayerCfg.JoystickDead[3] = PlayerCfg.JoystickDead[4] = PlayerCfg.JoystickDead[5] = 0;
  244.         PlayerCfg.JoystickLinear[0] = PlayerCfg.JoystickLinear[1] = PlayerCfg.JoystickLinear[2] = PlayerCfg.JoystickLinear[3] = PlayerCfg.JoystickLinear[4] = PlayerCfg.JoystickLinear[5] = 0;
  245.         PlayerCfg.JoystickSpeed[0] = PlayerCfg.JoystickSpeed[1] = PlayerCfg.JoystickSpeed[2] = PlayerCfg.JoystickSpeed[3] = PlayerCfg.JoystickSpeed[4] = PlayerCfg.JoystickSpeed[5] = 16;
  246.         PlayerCfg.MouseFlightSim = 0;
  247.         PlayerCfg.MouseSens[0] = PlayerCfg.MouseSens[1] = PlayerCfg.MouseSens[2] = PlayerCfg.MouseSens[3] = PlayerCfg.MouseSens[4] = PlayerCfg.MouseSens[5] = 8;
  248.         PlayerCfg.MouseOverrun[0] = PlayerCfg.MouseOverrun[1] = PlayerCfg.MouseOverrun[2] = PlayerCfg.MouseOverrun[3] = PlayerCfg.MouseOverrun[4] = PlayerCfg.MouseOverrun[5] = 0;
  249.         PlayerCfg.MouseFSDead = 0;
  250.         PlayerCfg.MouseFSIndicator = 1;
  251.         PlayerCfg.CockpitMode[0] = PlayerCfg.CockpitMode[1] = CM_FULL_COCKPIT;
  252.         PlayerCfg.ReticleType = RET_TYPE_CLASSIC;
  253.         PlayerCfg.ReticleRGBA[0] = RET_COLOR_DEFAULT_R; PlayerCfg.ReticleRGBA[1] = RET_COLOR_DEFAULT_G; PlayerCfg.ReticleRGBA[2] = RET_COLOR_DEFAULT_B; PlayerCfg.ReticleRGBA[3] = RET_COLOR_DEFAULT_A;
  254.         PlayerCfg.ReticleSize = 0;
  255.         PlayerCfg.HudMode = HudType::Standard;
  256. #if defined(DXX_BUILD_DESCENT_I)
  257.         PlayerCfg.BombGauge = 1;
  258. #elif defined(DXX_BUILD_DESCENT_II)
  259.         PlayerCfg.Cockpit3DView[0]=CV_NONE;
  260.         PlayerCfg.Cockpit3DView[1]=CV_NONE;
  261.         PlayerCfg.ThiefModifierFlags = 0;
  262.         PlayerCfg.MissileViewEnabled = MissileViewMode::EnabledSelfOnly;
  263.         PlayerCfg.HeadlightActiveDefault = 1;
  264.         PlayerCfg.GuidedInBigWindow = 0;
  265.         PlayerCfg.GuidebotName = "GUIDE-BOT";
  266.         PlayerCfg.GuidebotNameReal = PlayerCfg.GuidebotName;
  267.         PlayerCfg.EscortHotKeys = 1;
  268. #endif
  269.         PlayerCfg.PersistentDebris = 0;
  270.         PlayerCfg.PRShot = 0;
  271.         PlayerCfg.NoRedundancy = 0;
  272.         PlayerCfg.MultiMessages = 0;
  273.         PlayerCfg.MultiPingHud = 0;
  274.         PlayerCfg.NoRankings = 0;
  275.         PlayerCfg.AutomapFreeFlight = 0;
  276.         PlayerCfg.NoFireAutoselect = FiringAutoselectMode::Immediate;
  277.         PlayerCfg.CycleAutoselectOnly = 0;
  278.         PlayerCfg.CloakInvulTimer = 0;
  279.         PlayerCfg.AlphaEffects = 0;
  280.         PlayerCfg.DynLightColor = 0;
  281.  
  282.         // Default taunt macros
  283. #if defined(DXX_BUILD_DESCENT_I)
  284.         PlayerCfg.NetworkMessageMacro[0].copy_if(TXT_DEF_MACRO_1);
  285.         PlayerCfg.NetworkMessageMacro[1].copy_if(TXT_DEF_MACRO_2);
  286.         PlayerCfg.NetworkMessageMacro[2].copy_if(TXT_DEF_MACRO_3);
  287.         PlayerCfg.NetworkMessageMacro[3].copy_if(TXT_DEF_MACRO_4);
  288. #elif defined(DXX_BUILD_DESCENT_II)
  289.         PlayerCfg.NetworkMessageMacro[0] = "Why can't we all just get along?";
  290.         PlayerCfg.NetworkMessageMacro[1] = "Hey, I got a present for ya";
  291.         PlayerCfg.NetworkMessageMacro[2] = "I got a hankerin' for a spankerin'";
  292.         PlayerCfg.NetworkMessageMacro[3] = "This one's headed for Uranus";
  293. #endif
  294.         PlayerCfg.NetlifeKills=0; PlayerCfg.NetlifeKilled=0;
  295.        
  296.         return 1;
  297. }
  298. }
  299.  
  300. static int convert_pattern_array(const char *name, std::size_t namelen, int *array, std::size_t arraylen, const char *word, const char *line)
  301. {
  302.         if (memcmp(word, name, namelen - 1))
  303.                 return 0;
  304.         char *p;
  305.         unsigned long which = strtoul(word + namelen - 1, &p, 10);
  306.         if (*p || which >= arraylen)
  307.                 return 0;
  308.         array[which] = strtol(line, NULL, 10);
  309.         return 1;
  310. }
  311.  
  312. template <std::size_t namelen, std::size_t arraylen>
  313. static int convert_pattern_array(const char (&name)[namelen], std::array<int, arraylen> &array, const char *word, const char *line)
  314. {
  315.         return convert_pattern_array(name, namelen, &array[0], arraylen, word, line);
  316. }
  317.  
  318. static void print_pattern_array(PHYSFS_File *fout, const char *name, const int *array, std::size_t arraylen)
  319. {
  320.         for (std::size_t i = 0; i < arraylen; ++i)
  321.                 PHYSFSX_printf(fout,"%s%" DXX_PRI_size_type "=%d\n", name, i, array[i]);
  322. }
  323.  
  324. template <std::size_t arraylen>
  325. static void print_pattern_array(PHYSFS_File *fout, const char *name, const std::array<int, arraylen> &array)
  326. {
  327.         print_pattern_array(fout, name, &array[0], arraylen);
  328. }
  329.  
  330. static const char *splitword(char *line, char c)
  331. {
  332.         char *p = strchr(line, c);
  333.         if (p)
  334.         {
  335.                 *p = 0;
  336.                 return p + 1;
  337.         }
  338.         return p;
  339. }
  340.  
  341. namespace dsx {
  342. static void read_player_dxx(const char *filename)
  343. {
  344.         plyr_read_stats();
  345.  
  346.         auto f = PHYSFSX_openReadBuffered(filename);
  347.         if (!f)
  348.                 return;
  349.  
  350.         PlayerCfg.SPGameplayOptions.AutosaveInterval = std::chrono::minutes(10);
  351.         for (PHYSFSX_gets_line_t<50> line; PHYSFSX_fgets(line,f);)
  352.         {
  353. #if defined(DXX_BUILD_DESCENT_I)
  354.                 if (!strcmp(line, WEAPON_REORDER_HEADER_TEXT))
  355.                 {
  356.                         while(PHYSFSX_fgets(line, f) && strcmp(line, END_TEXT))
  357.                         {
  358.                                 const char *value=splitword(line,'=');
  359.                                 if (!value)
  360.                                         continue;
  361. #define CONVERT_WEAPON_REORDER_VALUE(A,F)       \
  362.         unsigned int wo0=0,wo1=0,wo2=0,wo3=0,wo4=0,wo5=0;       \
  363.         if (sscanf(value,F,&wo0, &wo1, &wo2, &wo3, &wo4, &wo5) == 6)    \
  364.                 A[0]=wo0, A[1]=wo1, A[2]=wo2, A[3]=wo3, A[4]=wo4, A[5]=wo5, check_weapon_reorder(A);
  365.                                 if(!strcmp(line,WEAPON_REORDER_PRIMARY_NAME_TEXT))
  366.                                 {
  367.                                         CONVERT_WEAPON_REORDER_VALUE(PlayerCfg.PrimaryOrder, WEAPON_REORDER_PRIMARY_VALUE_TEXT);
  368.                                 }
  369.                                 else if(!strcmp(line,WEAPON_REORDER_SECONDARY_NAME_TEXT))
  370.                                 {
  371.                                         CONVERT_WEAPON_REORDER_VALUE(PlayerCfg.SecondaryOrder, WEAPON_REORDER_SECONDARY_VALUE_TEXT);
  372.                                 }
  373.                         }
  374.                 }
  375.                 else
  376. #endif
  377.                 if (!strcmp(line,KEYBOARD_HEADER_TEXT))
  378.                 {
  379.                         while(PHYSFSX_fgets(line, f) && strcmp(line, END_TEXT))
  380.                         {
  381.                                 const char *value=splitword(line,'=');
  382.                                 if (!value)
  383.                                         continue;
  384.                                 convert_pattern_array(SENSITIVITY_NAME_TEXT, PlayerCfg.KeyboardSens, line, value);
  385.                         }
  386.                 }
  387.                 else if (!strcmp(line,JOYSTICK_HEADER_TEXT))
  388.                 {
  389.                         while(PHYSFSX_fgets(line, f) && strcmp(line, END_TEXT))
  390.                         {
  391.                                 const char *value=splitword(line,'=');
  392.                                 if (!value)
  393.                                         continue;
  394.                                 convert_pattern_array(SENSITIVITY_NAME_TEXT, PlayerCfg.JoystickSens, line, value) ||
  395.                                 convert_pattern_array(LINEAR_NAME_TEXT, PlayerCfg.JoystickLinear, line, value) ||
  396.                                 convert_pattern_array(SPEED_NAME_TEXT, PlayerCfg.JoystickSpeed, line, value) ||
  397.                                 convert_pattern_array(DEADZONE_NAME_TEXT, PlayerCfg.JoystickDead, line, value);
  398.                         }
  399.                 }
  400.                 else if (!strcmp(line,MOUSE_HEADER_TEXT))
  401.                 {
  402.                         while(PHYSFSX_fgets(line, f) && strcmp(line, END_TEXT))
  403.                         {
  404.                                 const char *value=splitword(line,'=');
  405.                                 if (!value)
  406.                                         continue;
  407.                                 if(!strcmp(line,MOUSE_FLIGHTSIM_NAME_TEXT))
  408.                                         PlayerCfg.MouseFlightSim = atoi(value);
  409.                                 else if (convert_pattern_array(SENSITIVITY_NAME_TEXT, PlayerCfg.MouseSens, line, value))
  410.                                         ;
  411.                                 else if (convert_pattern_array(MOUSE_OVERRUN_NAME_TEXT, PlayerCfg.MouseOverrun, line, value))
  412.                                         ;
  413.                                 else if(!strcmp(line,MOUSE_FSDEAD_NAME_TEXT))
  414.                                         PlayerCfg.MouseFSDead = atoi(value);
  415.                                 else if(!strcmp(line,MOUSE_FSINDICATOR_NAME_TEXT))
  416.                                         PlayerCfg.MouseFSIndicator = atoi(value);
  417.                         }
  418.                 }
  419.                 else if (!strcmp(line,WEAPON_KEYv2_HEADER_TEXT))
  420.                 {
  421.                         while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT))
  422.                         {
  423.                                 const char *value=splitword(line,'=');
  424.                                 if (!value)
  425.                                         continue;
  426.                                 unsigned int kc1=0,kc2=0,kc3=0;
  427.                                 int i=atoi(line);
  428.                                
  429.                                 if(i==0) i=10;
  430.                                         i=(i-1)*3;
  431.                
  432.                                 if (sscanf(value,WEAPON_KEYv2_VALUE_TEXT,&kc1,&kc2,&kc3) != 3)
  433.                                         continue;
  434.                                 PlayerCfg.KeySettingsRebirth[i]   = kc1;
  435.                                 PlayerCfg.KeySettingsRebirth[i+1] = kc2;
  436.                                 PlayerCfg.KeySettingsRebirth[i+2] = kc3;
  437.                         }
  438.                 }
  439.                 else if (!strcmp(line,COCKPIT_HEADER_TEXT))
  440.                 {
  441.                         while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT))
  442.                         {
  443.                                 const char *value=splitword(line,'=');
  444.                                 if (!value)
  445.                                         continue;
  446. #if defined(DXX_BUILD_DESCENT_I)
  447.                                 if(!strcmp(line,COCKPIT_MODE_NAME_TEXT))
  448.                                         PlayerCfg.CockpitMode[0] = PlayerCfg.CockpitMode[1] = static_cast<cockpit_mode_t>(atoi(value));
  449.                                 else
  450. #endif
  451.                                 if(!strcmp(line,COCKPIT_HUD_NAME_TEXT))
  452.                                         PlayerCfg.HudMode = static_cast<HudType>(atoi(value));
  453.                                 else if(!strcmp(line,COCKPIT_RETICLE_TYPE_NAME_TEXT))
  454.                                         PlayerCfg.ReticleType = atoi(value);
  455.                                 else if(!strcmp(line,COCKPIT_RETICLE_COLOR_NAME_TEXT))
  456.                                         sscanf(value,"%i,%i,%i,%i",&PlayerCfg.ReticleRGBA[0],&PlayerCfg.ReticleRGBA[1],&PlayerCfg.ReticleRGBA[2],&PlayerCfg.ReticleRGBA[3]);
  457.                                 else if(!strcmp(line,COCKPIT_RETICLE_SIZE_NAME_TEXT))
  458.                                         PlayerCfg.ReticleSize = atoi(value);
  459.                         }
  460.                 }
  461.                 else if (!strcmp(line,TOGGLES_HEADER_TEXT))
  462.                 {
  463.                         PlayerCfg.MouselookFlags = 0;
  464. #if defined(DXX_BUILD_DESCENT_II)
  465.                         PlayerCfg.ThiefModifierFlags = 0;
  466. #endif
  467.                         while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT))
  468.                         {
  469.                                 const char *value=splitword(line,'=');
  470.                                 if (!value)
  471.                                         continue;
  472. #if defined(DXX_BUILD_DESCENT_I)
  473.                                 if(!strcmp(line,TOGGLES_BOMBGAUGE_NAME_TEXT))
  474.                                         PlayerCfg.BombGauge = atoi(value);
  475. #elif defined(DXX_BUILD_DESCENT_II)
  476.                                 if(!strcmp(line,TOGGLES_ESCORTHOTKEYS_NAME_TEXT))
  477.                                         PlayerCfg.EscortHotKeys = atoi(value);
  478.                                 else if (!strcmp(line, TOGGLES_THIEF_ABSENCE_SP))
  479.                                 {
  480.                                         if (strtoul(value, 0, 10))
  481.                                                 PlayerCfg.ThiefModifierFlags |= ThiefModifier::Absent;
  482.                                 }
  483.                                 else if (!strcmp(line, TOGGLES_THIEF_NO_ENERGY_WEAPONS_SP))
  484.                                 {
  485.                                         if (strtoul(value, 0, 10))
  486.                                                 PlayerCfg.ThiefModifierFlags |= ThiefModifier::NoEnergyWeapons;
  487.                                 }
  488. #endif
  489.                                 else if (!strcmp(line, TOGGLES_AUTOSAVE_INTERVAL_SP))
  490.                                 {
  491.                                         const auto l = strtoul(value, 0, 10);
  492.                                         PlayerCfg.SPGameplayOptions.AutosaveInterval = std::chrono::seconds(l);
  493.                                 }
  494.                                 if(!strcmp(line,TOGGLES_PERSISTENTDEBRIS_NAME_TEXT))
  495.                                         PlayerCfg.PersistentDebris = atoi(value);
  496.                                 if(!strcmp(line,TOGGLES_PRSHOT_NAME_TEXT))
  497.                                         PlayerCfg.PRShot = atoi(value);
  498.                                 if(!strcmp(line,TOGGLES_NOREDUNDANCY_NAME_TEXT))
  499.                                         PlayerCfg.NoRedundancy = atoi(value);
  500.                                 if(!strcmp(line,TOGGLES_MULTIMESSAGES_NAME_TEXT))
  501.                                         PlayerCfg.MultiMessages = atoi(value);
  502.                                 if(!strcmp(line,TOGGLES_MULTIPINGHUD_NAME_TEXT))
  503.                                         PlayerCfg.MultiPingHud = atoi(value);
  504.                                 if(!strcmp(line,TOGGLES_NORANKINGS_NAME_TEXT))
  505.                                         PlayerCfg.NoRankings = atoi(value);
  506.                                 if(!strcmp(line,TOGGLES_AUTOMAPFREEFLIGHT_NAME_TEXT))
  507.                                         PlayerCfg.AutomapFreeFlight = atoi(value);
  508.                                 if(!strcmp(line,TOGGLES_NOFIREAUTOSELECT_NAME_TEXT))
  509.                                         PlayerCfg.NoFireAutoselect = static_cast<FiringAutoselectMode>(atoi(value));
  510.                                 if(!strcmp(line,TOGGLES_CYCLEAUTOSELECTONLY_NAME_TEXT))
  511.                                         PlayerCfg.CycleAutoselectOnly = atoi(value);
  512.                                 if(!strcmp(line,TOGGLES_CLOAKINVULTIMER_NAME_TEXT))
  513.                                         PlayerCfg.CloakInvulTimer = atoi(value);
  514.                                 else if (!strcmp(line, TOGGLES_RESPAWN_ANY_KEY))
  515.                                         PlayerCfg.RespawnMode = static_cast<RespawnPress>(atoi(value));
  516.                                 else if (!strcmp(line, TOGGLES_MOUSELOOK))
  517.                                         PlayerCfg.MouselookFlags = strtoul(value, 0, 10);
  518.                         }
  519.                 }
  520.                 else if (!strcmp(line,GRAPHICS_HEADER_TEXT))
  521.                 {
  522.                         while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT))
  523.                         {
  524.                                 const char *value=splitword(line,'=');
  525.                                 if (!value)
  526.                                         continue;
  527.                                 if(!strcmp(line,GRAPHICS_ALPHAEFFECTS_NAME_TEXT))
  528.                                         PlayerCfg.AlphaEffects = atoi(value);
  529.                                 if(!strcmp(line,GRAPHICS_DYNLIGHTCOLOR_NAME_TEXT))
  530.                                         PlayerCfg.DynLightColor = atoi(value);
  531.                         }
  532.                 }
  533.                 else if (!strcmp(line,PLX_VERSION_HEADER_TEXT)) // know the version this pilot was used last with - allow modifications
  534.                 {
  535. #if 0
  536.                         int v1=0,v2=0,v3=0;
  537. #endif
  538.                         while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT))
  539.                         {
  540. #if 0
  541.                                 const char *value=splitword(line,'=');
  542.                                 if (!value)
  543.                                         continue;
  544.                                 sscanf(value,"%i.%i.%i",&v1,&v2,&v3);
  545. #endif
  546.                         }
  547.                 }
  548.                 else if (!strcmp(line,END_TEXT))
  549.                 {
  550.                         break;
  551.                 }
  552.                 else if (!strcmp(line,PLX_OPTION_HEADER_TEXT))
  553.                 {
  554.                         // No action needed
  555.                 }
  556.                 else
  557.                 {
  558.                         while(PHYSFSX_fgets(line,f) && strcmp(line,END_TEXT))
  559.                         {
  560.                         }
  561.                 }
  562.         }
  563. }
  564.  
  565. #if defined(DXX_BUILD_DESCENT_I)
  566. constexpr char effcode1[]="d1xrocks_SKCORX!D";
  567. constexpr char effcode2[]="AObe)7Rn1 -+/zZ'0";
  568. constexpr char effcode3[]="aoeuidhtnAOEUIDH6";
  569. constexpr char effcode4[]="'/.;]<{=,+?|}->[3";
  570.  
  571. static const uint8_t *decode_stat(const uint8_t *p,int *v,const char *effcode)
  572. {
  573.         unsigned char c;
  574.         int neg,i;
  575.  
  576.         if (p[0]==0)
  577.                 return NULL;
  578.         else if (p[0]>='a'){
  579.                 neg=1;/*I=p[0]-'a';*/
  580.         }else{
  581.                 neg=0;/*I=p[0]-'A';*/
  582.         }
  583.         i=0;*v=0;
  584.         p++;
  585.         while (p[i*2] && p[i*2+1] && p[i*2]!=' '){
  586.                 c=(p[i*2]-33)+((p[i*2+1]-33)<<4);
  587.                 c^=effcode[i+neg];
  588.                 *v+=c << (i*8);
  589.                 i++;
  590.         }
  591.         if (neg)
  592.              *v *= -1;
  593.         if (!p[i*2])
  594.                 return NULL;
  595.         return p+(i*2);
  596. }
  597.  
  598. static void plyr_read_stats_v(int *k, int *d)
  599. {
  600.         char filename[PATH_MAX];
  601.         int k1=-1,k2=0,d1=-1,d2=0;
  602.         *k=0;*d=0;//in case the file doesn't exist.
  603.         memset(filename, '\0', PATH_MAX);
  604.         snprintf(filename,sizeof(filename),PLAYER_EFFECTIVENESS_FILENAME_FORMAT,static_cast<const char *>(get_local_player().callsign));
  605.         if (auto f = PHYSFSX_openReadBuffered(filename))
  606.         {
  607.                 PHYSFSX_gets_line_t<256> line;
  608.                 if(!PHYSFS_eof(f))
  609.                 {
  610.                          PHYSFSX_fgets(line,f);
  611.                          const char *value=splitword(line,':');
  612.                          if(!strcmp(line,"kills") && value)
  613.                                 *k=atoi(value);
  614.                 }
  615.                 if(!PHYSFS_eof(f))
  616.                 {
  617.                          PHYSFSX_fgets(line,f);
  618.                          const char *value=splitword(line,':');
  619.                          if(!strcmp(line,"deaths") && value)
  620.                                 *d=atoi(value);
  621.                  }
  622.                 if(!PHYSFS_eof(f))
  623.                 {
  624.                          PHYSFSX_fgets(line,f);
  625.                          const char *value=splitword(line,':');
  626.                          if(value && !strcmp(line,"key") && strlen(value)>10){
  627.                                  if (value[0]=='0' && value[1]=='1'){
  628.                                          const uint8_t *p;
  629.                                          if ((p=decode_stat(reinterpret_cast<const unsigned char *>(value + 3), &k1, effcode1))&&
  630.                                              (p=decode_stat(p+1,&k2,effcode2))&&
  631.                                              (p=decode_stat(p+1,&d1,effcode3))){
  632.                                                  decode_stat(p+1,&d2,effcode4);
  633.                                          }
  634.                                  }
  635.                          }
  636.                 }
  637.                 if (k1!=k2 || k1!=*k || d1!=d2 || d1!=*d)
  638.                 {
  639.                         *k=0;*d=0;
  640.                 }
  641.         }
  642. }
  643.  
  644. static void plyr_read_stats()
  645. {
  646.         plyr_read_stats_v(&PlayerCfg.NetlifeKills,&PlayerCfg.NetlifeKilled);
  647. }
  648.  
  649. void plyr_save_stats()
  650. {
  651.         int kills = PlayerCfg.NetlifeKills,deaths = PlayerCfg.NetlifeKilled, neg, i;
  652.         char filename[PATH_MAX];
  653.         std::array<uint8_t, 16> buf, buf2;
  654.         uint8_t a;
  655.         memset(filename, '\0', PATH_MAX);
  656.         snprintf(filename,sizeof(filename),PLAYER_EFFECTIVENESS_FILENAME_FORMAT,static_cast<const char *>(get_local_player().callsign));
  657.         auto f = PHYSFSX_openWriteBuffered(filename);
  658.         if(!f)
  659.                 return; //broken!
  660.  
  661.         PHYSFSX_printf(f,"kills:%i\n",kills);
  662.         PHYSFSX_printf(f,"deaths:%i\n",deaths);
  663.         PHYSFSX_printf(f,"key:01 ");
  664.  
  665.         if (kills < 0)
  666.         {
  667.                 neg=1;
  668.                 kills*=-1;
  669.         }
  670.         else
  671.                 neg=0;
  672.  
  673.         for (i=0;kills;i++)
  674.         {
  675.                 a=(kills & 0xFF) ^ effcode1[i+neg];
  676.                 buf[i*2]=(a&0xF)+33;
  677.                 buf[i*2+1]=(a>>4)+33;
  678.                 a=(kills & 0xFF) ^ effcode2[i+neg];
  679.                 buf2[i*2]=(a&0xF)+33;
  680.                 buf2[i*2+1]=(a>>4)+33;
  681.                 kills>>=8;
  682.         }
  683.  
  684.         buf[i*2]=0;
  685.         buf2[i*2]=0;
  686.  
  687.         if (neg)
  688.                 i+='a';
  689.         else
  690.                 i+='A';
  691.  
  692.         PHYSFSX_printf(f,"%c%s %c%s ",i,buf.data(),i,buf2.data());
  693.  
  694.         if (deaths < 0)
  695.         {
  696.                 neg=1;
  697.                 deaths*=-1;
  698.         }else
  699.                 neg=0;
  700.  
  701.         for (i=0;deaths;i++)
  702.         {
  703.                 a=(deaths & 0xFF) ^ effcode3[i+neg];
  704.                 buf[i*2]=(a&0xF)+33;
  705.                 buf[i*2+1]=(a>>4)+33;
  706.                 a=(deaths & 0xFF) ^ effcode4[i+neg];
  707.                 buf2[i*2]=(a&0xF)+33;
  708.                 buf2[i*2+1]=(a>>4)+33;
  709.                 deaths>>=8;
  710.         }
  711.  
  712.         buf[i*2]=0;
  713.         buf2[i*2]=0;
  714.  
  715.         if (neg)
  716.                 i+='a';
  717.         else
  718.                 i+='A';
  719.  
  720.         PHYSFSX_printf(f, "%c%s %c%s\n", i, buf.data(), i, buf2.data());
  721. }
  722. #endif
  723.  
  724. static int write_player_dxx(const char *filename)
  725. {
  726.         int rc=0;
  727.         char tempfile[PATH_MAX];
  728.  
  729.         strcpy(tempfile,filename);
  730.         tempfile[strlen(tempfile)-4]=0;
  731.         strcat(tempfile,".pl$");
  732.         auto fout = PHYSFSX_openWriteBuffered(tempfile);
  733.         if (!fout && CGameArg.SysUsePlayersDir)
  734.         {
  735.                 PHYSFS_mkdir(PLAYER_DIRECTORY_STRING(""));      //try making directory
  736.                 fout=PHYSFSX_openWriteBuffered(tempfile);
  737.         }
  738.        
  739.         if(fout)
  740.         {
  741.                 PHYSFSX_printf(fout,PLX_OPTION_HEADER_TEXT "\n");
  742. #if defined(DXX_BUILD_DESCENT_I)
  743.                 PHYSFSX_printf(fout,WEAPON_REORDER_HEADER_TEXT "\n");
  744.                 PHYSFSX_printf(fout,WEAPON_REORDER_PRIMARY_NAME_TEXT "=" WEAPON_REORDER_PRIMARY_VALUE_TEXT "\n",PlayerCfg.PrimaryOrder[0], PlayerCfg.PrimaryOrder[1], PlayerCfg.PrimaryOrder[2],PlayerCfg.PrimaryOrder[3], PlayerCfg.PrimaryOrder[4], PlayerCfg.PrimaryOrder[5]);
  745.                 PHYSFSX_printf(fout,WEAPON_REORDER_SECONDARY_NAME_TEXT "=" WEAPON_REORDER_SECONDARY_VALUE_TEXT "\n",PlayerCfg.SecondaryOrder[0], PlayerCfg.SecondaryOrder[1], PlayerCfg.SecondaryOrder[2],PlayerCfg.SecondaryOrder[3], PlayerCfg.SecondaryOrder[4], PlayerCfg.SecondaryOrder[5]);
  746.                 PHYSFSX_printf(fout,END_TEXT "\n");
  747. #endif
  748.                 PHYSFSX_printf(fout,KEYBOARD_HEADER_TEXT "\n");
  749.                 print_pattern_array(fout, SENSITIVITY_NAME_TEXT, PlayerCfg.KeyboardSens);
  750.                 PHYSFSX_printf(fout,END_TEXT "\n");
  751.                 PHYSFSX_printf(fout,JOYSTICK_HEADER_TEXT "\n");
  752.                 print_pattern_array(fout, SENSITIVITY_NAME_TEXT, PlayerCfg.JoystickSens);
  753.                 print_pattern_array(fout, LINEAR_NAME_TEXT, PlayerCfg.JoystickLinear);
  754.                 print_pattern_array(fout, SPEED_NAME_TEXT, PlayerCfg.JoystickSpeed);
  755.                 print_pattern_array(fout, DEADZONE_NAME_TEXT, PlayerCfg.JoystickDead);
  756.                 PHYSFSX_printf(fout,END_TEXT "\n");
  757.                 PHYSFSX_printf(fout,MOUSE_HEADER_TEXT "\n");
  758.                 PHYSFSX_printf(fout,MOUSE_FLIGHTSIM_NAME_TEXT "=" MOUSE_FLIGHTSIM_VALUE_TEXT "\n",PlayerCfg.MouseFlightSim);
  759.                 print_pattern_array(fout, SENSITIVITY_NAME_TEXT, PlayerCfg.MouseSens);
  760.                 print_pattern_array(fout, MOUSE_OVERRUN_NAME_TEXT, PlayerCfg.MouseOverrun);
  761.                 PHYSFSX_printf(fout,MOUSE_FSDEAD_NAME_TEXT "=" MOUSE_FSDEAD_VALUE_TEXT "\n",PlayerCfg.MouseFSDead);
  762.                 PHYSFSX_printf(fout,MOUSE_FSINDICATOR_NAME_TEXT "=" MOUSE_FSINDICATOR_VALUE_TEXT "\n",PlayerCfg.MouseFSIndicator);
  763.                 PHYSFSX_printf(fout,END_TEXT "\n");
  764.                 PHYSFSX_printf(fout,WEAPON_KEYv2_HEADER_TEXT "\n");
  765.                 PHYSFSX_printf(fout,"1=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[0],PlayerCfg.KeySettingsRebirth[1],PlayerCfg.KeySettingsRebirth[2]);
  766.                 PHYSFSX_printf(fout,"2=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[3],PlayerCfg.KeySettingsRebirth[4],PlayerCfg.KeySettingsRebirth[5]);
  767.                 PHYSFSX_printf(fout,"3=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[6],PlayerCfg.KeySettingsRebirth[7],PlayerCfg.KeySettingsRebirth[8]);
  768.                 PHYSFSX_printf(fout,"4=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[9],PlayerCfg.KeySettingsRebirth[10],PlayerCfg.KeySettingsRebirth[11]);
  769.                 PHYSFSX_printf(fout,"5=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[12],PlayerCfg.KeySettingsRebirth[13],PlayerCfg.KeySettingsRebirth[14]);
  770.                 PHYSFSX_printf(fout,"6=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[15],PlayerCfg.KeySettingsRebirth[16],PlayerCfg.KeySettingsRebirth[17]);
  771.                 PHYSFSX_printf(fout,"7=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[18],PlayerCfg.KeySettingsRebirth[19],PlayerCfg.KeySettingsRebirth[20]);
  772.                 PHYSFSX_printf(fout,"8=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[21],PlayerCfg.KeySettingsRebirth[22],PlayerCfg.KeySettingsRebirth[23]);
  773.                 PHYSFSX_printf(fout,"9=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[24],PlayerCfg.KeySettingsRebirth[25],PlayerCfg.KeySettingsRebirth[26]);
  774.                 PHYSFSX_printf(fout,"0=" WEAPON_KEYv2_VALUE_TEXT "\n",PlayerCfg.KeySettingsRebirth[27],PlayerCfg.KeySettingsRebirth[28],PlayerCfg.KeySettingsRebirth[29]);
  775.                 PHYSFSX_printf(fout,END_TEXT "\n");
  776.                 PHYSFSX_printf(fout,COCKPIT_HEADER_TEXT "\n");
  777. #if defined(DXX_BUILD_DESCENT_I)
  778.                 PHYSFSX_printf(fout,COCKPIT_MODE_NAME_TEXT "=%i\n",PlayerCfg.CockpitMode[0]);
  779. #endif
  780.                 PHYSFSX_printf(fout,COCKPIT_HUD_NAME_TEXT "=%u\n", static_cast<unsigned>(PlayerCfg.HudMode));
  781.                 PHYSFSX_printf(fout,COCKPIT_RETICLE_TYPE_NAME_TEXT "=%i\n",PlayerCfg.ReticleType);
  782.                 PHYSFSX_printf(fout,COCKPIT_RETICLE_COLOR_NAME_TEXT "=%i,%i,%i,%i\n",PlayerCfg.ReticleRGBA[0],PlayerCfg.ReticleRGBA[1],PlayerCfg.ReticleRGBA[2],PlayerCfg.ReticleRGBA[3]);
  783.                 PHYSFSX_printf(fout,COCKPIT_RETICLE_SIZE_NAME_TEXT "=%i\n",PlayerCfg.ReticleSize);
  784.                 PHYSFSX_printf(fout,END_TEXT "\n");
  785.                 PHYSFSX_printf(fout,TOGGLES_HEADER_TEXT "\n");
  786. #if defined(DXX_BUILD_DESCENT_I)
  787.                 PHYSFSX_printf(fout,TOGGLES_BOMBGAUGE_NAME_TEXT "=%i\n",PlayerCfg.BombGauge);
  788. #elif defined(DXX_BUILD_DESCENT_II)
  789.                 PHYSFSX_printf(fout,TOGGLES_ESCORTHOTKEYS_NAME_TEXT "=%i\n",PlayerCfg.EscortHotKeys);
  790.                 PHYSFSX_printf(fout, TOGGLES_THIEF_ABSENCE_SP "=%i\n", PlayerCfg.ThiefModifierFlags & ThiefModifier::Absent);
  791.                 PHYSFSX_printf(fout, TOGGLES_THIEF_NO_ENERGY_WEAPONS_SP "=%i\n", PlayerCfg.ThiefModifierFlags & ThiefModifier::NoEnergyWeapons);
  792. #endif
  793.                 PHYSFSX_printf(fout, TOGGLES_AUTOSAVE_INTERVAL_SP "=%i\n", PlayerCfg.SPGameplayOptions.AutosaveInterval.count());
  794.                 PHYSFSX_printf(fout,TOGGLES_PERSISTENTDEBRIS_NAME_TEXT "=%i\n",PlayerCfg.PersistentDebris);
  795.                 PHYSFSX_printf(fout,TOGGLES_PRSHOT_NAME_TEXT "=%i\n",PlayerCfg.PRShot);
  796.                 PHYSFSX_printf(fout,TOGGLES_NOREDUNDANCY_NAME_TEXT "=%i\n",PlayerCfg.NoRedundancy);
  797.                 PHYSFSX_printf(fout,TOGGLES_MULTIMESSAGES_NAME_TEXT "=%i\n",PlayerCfg.MultiMessages);
  798.                 PHYSFSX_printf(fout,TOGGLES_MULTIPINGHUD_NAME_TEXT "=%i\n",PlayerCfg.MultiPingHud);
  799.                 PHYSFSX_printf(fout,TOGGLES_NORANKINGS_NAME_TEXT "=%i\n",PlayerCfg.NoRankings);
  800.                 PHYSFSX_printf(fout,TOGGLES_AUTOMAPFREEFLIGHT_NAME_TEXT "=%i\n",PlayerCfg.AutomapFreeFlight);
  801.                 PHYSFSX_printf(fout,TOGGLES_NOFIREAUTOSELECT_NAME_TEXT "=%i\n",static_cast<unsigned>(PlayerCfg.NoFireAutoselect));
  802.                 PHYSFSX_printf(fout,TOGGLES_CYCLEAUTOSELECTONLY_NAME_TEXT "=%i\n",PlayerCfg.CycleAutoselectOnly);
  803.                 PHYSFSX_printf(fout,TOGGLES_CLOAKINVULTIMER_NAME_TEXT "=%i\n",PlayerCfg.CloakInvulTimer);
  804.                 PHYSFSX_printf(fout,TOGGLES_RESPAWN_ANY_KEY "=%i\n",static_cast<unsigned>(PlayerCfg.RespawnMode));
  805.                 PHYSFSX_printf(fout, TOGGLES_MOUSELOOK "=%i\n", PlayerCfg.MouselookFlags);
  806.                 PHYSFSX_printf(fout,END_TEXT "\n");
  807.                 PHYSFSX_printf(fout,GRAPHICS_HEADER_TEXT "\n");
  808.                 PHYSFSX_printf(fout,GRAPHICS_ALPHAEFFECTS_NAME_TEXT "=%i\n",PlayerCfg.AlphaEffects);
  809.                 PHYSFSX_printf(fout,GRAPHICS_DYNLIGHTCOLOR_NAME_TEXT "=%i\n",PlayerCfg.DynLightColor);
  810.                 PHYSFSX_printf(fout,END_TEXT "\n");
  811.                 PHYSFSX_printf(fout,PLX_VERSION_HEADER_TEXT "\n");
  812.                 PHYSFSX_printf(fout,"plx version=" DXX_VERSION_STR "\n");
  813.                 PHYSFSX_printf(fout,END_TEXT "\n");
  814.                 PHYSFSX_printf(fout,END_TEXT "\n");
  815.                 fout.reset();
  816.                 if(rc==0)
  817.                 {
  818.                         PHYSFS_delete(filename);
  819.                         rc = PHYSFSX_rename(tempfile,filename);
  820.                 }
  821.                 return rc;
  822.         }
  823.         else
  824.                 return errno;
  825. }
  826.  
  827. //read in the player's saved games.  returns errno (0 == no error)
  828. int read_player_file()
  829. {
  830.         char filename[PATH_MAX];
  831. #if defined(DXX_BUILD_DESCENT_I)
  832.         int shareware_file = -1;
  833.         int player_file_size;
  834. #elif defined(DXX_BUILD_DESCENT_II)
  835.         int rewrite_it=0;
  836.         int swap = 0;
  837.         short player_file_version;
  838. #endif
  839.  
  840.         Assert(Player_num < MAX_PLAYERS);
  841.  
  842.         memset(filename, '\0', PATH_MAX);
  843.         snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.plr"), static_cast<const char *>(InterfaceUniqueState.PilotName));
  844.         if (!PHYSFSX_exists(filename,0))
  845.                 return ENOENT;
  846.         auto file = PHYSFSX_openReadBuffered(filename);
  847.         if (!file)
  848.                 goto read_player_file_failed;
  849.  
  850.         new_player_config(); // Set defaults!
  851.  
  852. #if defined(DXX_BUILD_DESCENT_I)
  853.         // Unfortunatly d1x has been writing both shareware and registered
  854.         // player files with a saved_game_version of 7 and 8, whereas the
  855.         // original decent used 4 for shareware games and 7 for registered
  856.         // games. Because of this the player files didn't get properly read
  857.         // when reading d1x shareware player files in d1x registered or
  858.         // vica versa. The problem is that the sizeof of the taunt macros
  859.         // differ between the share and registered versions, causing the
  860.         // reading of the player file to go wrong. Thus we now determine the
  861.         // sizeof the player file to determine what kinda player file we are
  862.         // dealing with so that we can do the right thing
  863.         PHYSFS_seek(file, 0);
  864.         player_file_size = PHYSFS_fileLength(file);
  865. #endif
  866.         int id;
  867.         PHYSFS_readSLE32(file, &id);
  868. #if defined(DXX_BUILD_DESCENT_I)
  869.         short saved_game_version, player_struct_version;
  870.         saved_game_version = PHYSFSX_readShort(file);
  871.         player_struct_version = PHYSFSX_readShort(file);
  872.         PlayerCfg.NHighestLevels = PHYSFSX_readInt(file);
  873.         {
  874.                 const unsigned u = PHYSFSX_readInt(file);
  875.                 PlayerCfg.DefaultDifficulty = cast_clamp_difficulty(u);
  876.         }
  877.         PlayerCfg.AutoLeveling = PHYSFSX_readInt(file);
  878. #elif defined(DXX_BUILD_DESCENT_II)
  879.         player_file_version = PHYSFSX_readShort(file);
  880. #endif
  881.  
  882.         if (id!=SAVE_FILE_ID) {
  883.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, "Invalid player file");
  884.                 return -1;
  885.         }
  886.  
  887. #if defined(DXX_BUILD_DESCENT_I)
  888.         if (saved_game_version < COMPATIBLE_SAVED_GAME_VERSION || player_struct_version < COMPATIBLE_PLAYER_STRUCT_VERSION) {
  889.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_ERROR_PLR_VERSION);
  890.                 return -1;
  891.         }
  892.  
  893.         /* determine if we're dealing with a shareware or registered playerfile */
  894.         switch (saved_game_version)
  895.         {
  896.                 case 4:
  897.                         shareware_file = 1;
  898.                         break;
  899.                 case 5:
  900.                 case 6:
  901.                         shareware_file = 0;
  902.                         break;
  903.                 case 7:
  904.                         /* version 7 doesn't have the saved games array */
  905.                         if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == (2212 - sizeof(saved_games)))
  906.                                 shareware_file = 1;
  907.                         if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == (2252 - sizeof(saved_games)))
  908.                                 shareware_file = 0;
  909.                         break;
  910.                 case 8:
  911.                         if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == 2212)
  912.                                 shareware_file = 1;
  913.                         if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == 2252)
  914.                                 shareware_file = 0;
  915.                         /* d1x-rebirth v0.31 to v0.42 broke things by adding stuff to the
  916.                            player struct without thinking (sigh) */
  917.                         if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == (2212 + 2*sizeof(int)))
  918.                         {
  919.  
  920.                                 shareware_file = 1;
  921.                                 /* skip the cruft added to the player_info struct */
  922.                                 PHYSFS_seek(file, PHYSFS_tell(file)+2*sizeof(int));
  923.                         }
  924.                         if ((player_file_size - (sizeof(hli)*PlayerCfg.NHighestLevels)) == (2252 + 2*sizeof(int)))
  925.                         {
  926.                                 shareware_file = 0;
  927.                                 /* skip the cruft added to the player_info struct */
  928.                                 PHYSFS_seek(file, PHYSFS_tell(file)+2*sizeof(int));
  929.                         }
  930.         }
  931.  
  932.         if (shareware_file == -1) {
  933.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, "Error invalid or unknown\nplayerfile-size");
  934.                 return -1;
  935.         }
  936.  
  937.         if (saved_game_version <= 5) {
  938.  
  939.                 //deal with old-style highest level info
  940.  
  941.                 PlayerCfg.HighestLevels[0].Shortname[0] = 0;                                                    //no name for mission 0
  942.                 PlayerCfg.HighestLevels[0].LevelNum = PlayerCfg.NHighestLevels; //was highest level in old struct
  943.  
  944.                 //This hack allows the player to start on level 8 if he's made it to
  945.                 //level 7 on the shareware.  We do this because the shareware didn't
  946.                 //save the information that the player finished level 7, so the most
  947.                 //we know is that he made it to level 7.
  948.                 if (PlayerCfg.NHighestLevels==7)
  949.                         PlayerCfg.HighestLevels[0].LevelNum = 8;
  950.                
  951.         }
  952.         else {  //read new highest level info
  953.                 if (PHYSFS_read(file,PlayerCfg.HighestLevels,sizeof(hli),PlayerCfg.NHighestLevels) != PlayerCfg.NHighestLevels)
  954.                         goto read_player_file_failed;
  955.         }
  956.  
  957.         if ( saved_game_version != 7 ) {        // Read old & SW saved games.
  958.                 if (PHYSFS_read(file,saved_games,sizeof(saved_games),1) != 1)
  959.                         goto read_player_file_failed;
  960.         }
  961.  
  962. #elif defined(DXX_BUILD_DESCENT_II)
  963.         if (player_file_version > 255) // bigendian file?
  964.                 swap = 1;
  965.  
  966.         if (swap)
  967.                 player_file_version = SWAPSHORT(player_file_version);
  968.  
  969.         if (player_file_version < COMPATIBLE_PLAYER_FILE_VERSION) {
  970.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, TXT_ERROR_PLR_VERSION);
  971.                 return -1;
  972.         }
  973.  
  974.         PHYSFS_seek(file,PHYSFS_tell(file)+2*sizeof(short)); //skip Game_window_w,Game_window_h
  975.         PlayerCfg.DefaultDifficulty = cast_clamp_difficulty(PHYSFSX_readByte(file));
  976.         PlayerCfg.AutoLeveling       = PHYSFSX_readByte(file);
  977.         PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(sbyte)); // skip ReticleOn
  978.         PlayerCfg.CockpitMode[0] = PlayerCfg.CockpitMode[1] = static_cast<cockpit_mode_t>(PHYSFSX_readByte(file));
  979.         PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(sbyte)); //skip Default_display_mode
  980.         {
  981.                 auto i = PHYSFSX_readByte(file);
  982.                 switch (i)
  983.                 {
  984.                         case static_cast<unsigned>(MissileViewMode::None):
  985.                         case static_cast<unsigned>(MissileViewMode::EnabledSelfOnly):
  986.                         case static_cast<unsigned>(MissileViewMode::EnabledSelfAndAllies):
  987.                                 break;
  988.                         default:
  989.                                 i = 0;
  990.                                 break;
  991.                 }
  992.                 PlayerCfg.MissileViewEnabled = static_cast<MissileViewMode>(i);
  993.         }
  994.         PlayerCfg.HeadlightActiveDefault  = PHYSFSX_readByte(file);
  995.         PlayerCfg.GuidedInBigWindow      = PHYSFSX_readByte(file);
  996.         if (player_file_version >= 19)
  997.                 PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(sbyte)); //skip Automap_always_hires
  998.  
  999.         //read new highest level info
  1000.  
  1001.         PlayerCfg.NHighestLevels = PHYSFSX_readShort(file);
  1002.         if (swap)
  1003.                 PlayerCfg.NHighestLevels = SWAPSHORT(PlayerCfg.NHighestLevels);
  1004.         Assert(PlayerCfg.NHighestLevels <= MAX_MISSIONS);
  1005.  
  1006.         if (PHYSFS_read(file, PlayerCfg.HighestLevels, sizeof(hli), PlayerCfg.NHighestLevels) != PlayerCfg.NHighestLevels)
  1007.                 goto read_player_file_failed;
  1008. #endif
  1009.  
  1010.         //read taunt macros
  1011.         {
  1012.                 int len;
  1013.  
  1014. #if defined(DXX_BUILD_DESCENT_I)
  1015.                 len = shareware_file? 25:35;
  1016. #elif defined(DXX_BUILD_DESCENT_II)
  1017.                 len = MAX_MESSAGE_LEN;
  1018. #endif
  1019.  
  1020.                 for (auto &i : PlayerCfg.NetworkMessageMacro)
  1021.                         if (PHYSFS_read(file, i, len, 1) != 1)
  1022.                                 goto read_player_file_failed;
  1023.         }
  1024.  
  1025.         //read kconfig data
  1026.         {
  1027.                 ubyte dummy_joy_sens;
  1028.  
  1029.                 if (PHYSFS_read(file, &PlayerCfg.KeySettings.Keyboard, sizeof(PlayerCfg.KeySettings.Keyboard), 1) != 1)
  1030.                         goto read_player_file_failed;
  1031. #if DXX_MAX_JOYSTICKS
  1032.                 auto &KeySettingsJoystick = PlayerCfg.KeySettings.Joystick;
  1033. #else
  1034.                 std::array<uint8_t, MAX_CONTROLS> KeySettingsJoystick;
  1035. #endif
  1036.                 if (PHYSFS_read(file, &KeySettingsJoystick, sizeof(KeySettingsJoystick), 1) != 1)
  1037.                         goto read_player_file_failed;
  1038.                 PHYSFS_seek( file, PHYSFS_tell(file)+(sizeof(ubyte)*MAX_CONTROLS*3) ); // Skip obsolete Flightstick/Thrustmaster/Gravis map fields
  1039.                 if (PHYSFS_read(file, &PlayerCfg.KeySettings.Mouse, sizeof(PlayerCfg.KeySettings.Mouse), 1) != 1)
  1040.                         goto read_player_file_failed;
  1041.                 PHYSFS_seek( file, PHYSFS_tell(file)+(sizeof(ubyte)*MAX_CONTROLS) ); // Skip obsolete Cyberman map field
  1042. #if defined(DXX_BUILD_DESCENT_I)
  1043.                 if (PHYSFS_read(file, &PlayerCfg.ControlType, sizeof(ubyte), 1 )!=1)
  1044. #elif defined(DXX_BUILD_DESCENT_II)
  1045.                 if (player_file_version>=20)
  1046.                         PHYSFS_seek( file, PHYSFS_tell(file)+(sizeof(ubyte)*MAX_CONTROLS) ); // Skip obsolete Winjoy map field
  1047.                 uint8_t control_type_dos, control_type_win;
  1048.                 if (PHYSFS_read(file, &control_type_dos, sizeof(ubyte), 1) != 1)
  1049.                         goto read_player_file_failed;
  1050.                 else if (player_file_version >= 21 && PHYSFS_read(file, &control_type_win, sizeof(ubyte), 1) != 1)
  1051. #endif
  1052.                         goto read_player_file_failed;
  1053.                 else if (PHYSFS_read(file, &dummy_joy_sens, sizeof(ubyte), 1) !=1 )
  1054.                         goto read_player_file_failed;
  1055.  
  1056. #if defined(DXX_BUILD_DESCENT_II)
  1057.                 PlayerCfg.ControlType = control_type_dos;
  1058.        
  1059.                 range_for (const unsigned i, xrange(11u))
  1060.                 {
  1061.                         PlayerCfg.PrimaryOrder[i] = PHYSFSX_readByte(file);
  1062.                         PlayerCfg.SecondaryOrder[i] = PHYSFSX_readByte(file);
  1063.                 }
  1064.                 check_weapon_reorder(PlayerCfg.PrimaryOrder);
  1065.                 check_weapon_reorder(PlayerCfg.SecondaryOrder);
  1066.  
  1067.                 if (player_file_version>=16)
  1068.                 {
  1069.                         PHYSFS_readSLE32(file, &PlayerCfg.Cockpit3DView[0]);
  1070.                         PHYSFS_readSLE32(file, &PlayerCfg.Cockpit3DView[1]);
  1071.                         if (swap)
  1072.                         {
  1073.                                 PlayerCfg.Cockpit3DView[0] = SWAPINT(PlayerCfg.Cockpit3DView[0]);
  1074.                                 PlayerCfg.Cockpit3DView[1] = SWAPINT(PlayerCfg.Cockpit3DView[1]);
  1075.                         }
  1076.                 }
  1077. #endif
  1078.         }
  1079.  
  1080. #if defined(DXX_BUILD_DESCENT_I)
  1081.         if ( saved_game_version != 7 )  {
  1082.                 int i, found=0;
  1083.                
  1084.                 Assert( N_SAVE_SLOTS == 10 );
  1085.  
  1086.                 for (i=0; i < N_SAVE_SLOTS; i++ )       {
  1087.                         if ( saved_games[i].name[0] )   {
  1088.                                 throw std::runtime_error("old save game not supported");
  1089.                                 // make sure we do not do this again, which would possibly overwrite
  1090.                                 // a new newstyle savegame
  1091.                                 saved_games[i].name[0] = 0;
  1092.                                 found++;
  1093.                         }
  1094.                 }
  1095.                 if (found)
  1096.                         write_player_file();
  1097.         }
  1098. #elif defined(DXX_BUILD_DESCENT_II)
  1099.         if (player_file_version>=22)
  1100.         {
  1101.                 PHYSFS_readSLE32(file, &PlayerCfg.NetlifeKills);
  1102.                 PHYSFS_readSLE32(file, &PlayerCfg.NetlifeKilled);
  1103.                 if (swap) {
  1104.                         PlayerCfg.NetlifeKills = SWAPINT(PlayerCfg.NetlifeKills);
  1105.                         PlayerCfg.NetlifeKilled = SWAPINT(PlayerCfg.NetlifeKilled);
  1106.                 }
  1107.         }
  1108.         else
  1109.         {
  1110.                 PlayerCfg.NetlifeKills=0; PlayerCfg.NetlifeKilled=0;
  1111.         }
  1112.  
  1113.         if (player_file_version>=23)
  1114.         {
  1115.                 int i;
  1116.                 PHYSFS_readSLE32(file, &i);
  1117.                 if (swap)
  1118.                         i = SWAPINT(i);
  1119.                 if (i!=get_lifetime_checksum (PlayerCfg.NetlifeKills,PlayerCfg.NetlifeKilled))
  1120.                 {
  1121.                         PlayerCfg.NetlifeKills=0; PlayerCfg.NetlifeKilled=0;
  1122.                         nm_messagebox(NULL, 1, "Shame on me", "Trying to cheat eh?");
  1123.                         rewrite_it=1;
  1124.                 }
  1125.         }
  1126.  
  1127.         //read guidebot name
  1128.         if (player_file_version >= 18)
  1129.                 PHYSFSX_fgets(PlayerCfg.GuidebotName, file);
  1130.         else
  1131.                 PlayerCfg.GuidebotName = "GUIDE-BOT";
  1132.         PlayerCfg.GuidebotNameReal = PlayerCfg.GuidebotName;
  1133.         {
  1134.                 if (player_file_version >= 24)
  1135.                 {
  1136.                         PHYSFSX_gets_line_t<128> buf;
  1137.                         PHYSFSX_fgets(buf, file);                       // Just read it in fpr DPS.
  1138.                 }
  1139.         }
  1140. #endif
  1141.  
  1142. #if defined(DXX_BUILD_DESCENT_II)
  1143.         if (rewrite_it)
  1144.                 write_player_file();
  1145. #endif
  1146.  
  1147.         filename[strlen(filename) - 4] = 0;
  1148.         strcat(filename, ".plx");
  1149.         read_player_dxx(filename);
  1150.         kc_set_controls();
  1151.  
  1152.         return EZERO;
  1153.  
  1154.  read_player_file_failed:
  1155.         nm_messagebox(TXT_ERROR, 1, TXT_OK, "%s\n\n%s", "Error reading PLR file", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); // Pierre-Marie Baty -- work around PHYSFS_getLastError() deprecation
  1156.         return -1;
  1157. }
  1158. }
  1159.  
  1160. /* Given a Mission_path, return a pair of pointers.
  1161.  * - If the mission cannot be saved, both pointers are nullptr.
  1162.  * - If the mission name was previously used, return a pointer to that
  1163.  *   entry and a pointer to end.
  1164.  * - If the mission name is not recorded, return a pointer to the first
  1165.  *   unused element (which may be end() if all elements are used) and a
  1166.  *   pointer to end().  The caller must check that the first unused
  1167.  *   element is not end().
  1168.  */
  1169. static std::array<std::array<hli, MAX_MISSIONS>::pointer, 2> find_hli_entry(const partial_range_t<hli *> &r, const Mission_path &m)
  1170. {
  1171.         const auto mission_filename = m.filename;
  1172.         const auto mission_length = std::distance(mission_filename, m.path.end());
  1173.         if (mission_length >= sizeof(hli::Shortname))
  1174.         {
  1175.                 /* Name is too long to store, so there will never be a match
  1176.                  * and it is never stored.
  1177.                  */
  1178.                 con_printf(CON_URGENT, DXX_STRINGIZE_FL(__FILE__, __LINE__, "warning: highest level information cannot be tracked because the mission name is too long to store (should be at most 8 bytes, but is \"%s\")."), &*mission_filename);
  1179.                 return {{nullptr, nullptr}};
  1180.         }
  1181.         const auto &&a = [p = &*mission_filename](const hli &h) {
  1182.                 return !d_stricmp(h.Shortname.data(), p);
  1183.         };
  1184.         const auto i = std::find_if(r.begin(), r.end(), a);
  1185.         return {{&*i, r.end()}};
  1186. }
  1187.  
  1188. //set a new highest level for player for this mission
  1189. void set_highest_level(const uint8_t best_levelnum_this_game)
  1190. {
  1191.         int ret;
  1192.  
  1193.         if ((ret=read_player_file()) != EZERO)
  1194.                 if (ret != ENOENT)              //if file doesn't exist, that's ok
  1195.                         return;
  1196.  
  1197.         const auto &&r = partial_range(PlayerCfg.HighestLevels, PlayerCfg.NHighestLevels);
  1198.         const auto &&i = find_hli_entry(r, *Current_mission);
  1199.         const auto ie = i[1];
  1200.         if (!ie)
  1201.                 return;
  1202.  
  1203.         auto ii = i[0];
  1204.         const hli *d1_preferred = nullptr;
  1205. #if defined(DXX_BUILD_DESCENT_II)
  1206.         const hli *d2_preferred = nullptr, *d2x_preferred = nullptr;
  1207. #endif
  1208.         range_for (auto &m, r)
  1209.         {
  1210.                 hli *preferred;
  1211.                 /* Swapping instead of rotating will perturb the order a bit,
  1212.                  * but this is a one-time fix to get builtin missions to
  1213.                  * reserved storage.
  1214.                  */
  1215.                 const auto ms = m.Shortname.data();
  1216.                 if (!d1_preferred && !strcmp(D1_MISSION_FILENAME, ms))
  1217.                         d1_preferred = preferred = &PlayerCfg.HighestLevels[0];
  1218. #if defined(DXX_BUILD_DESCENT_II)
  1219.                 else if (!d2_preferred && !strcmp(FULL_MISSION_FILENAME, ms))
  1220.                         d2_preferred = preferred = &PlayerCfg.HighestLevels[1];
  1221.                 else if (!d2x_preferred && !strcmp("d2x", ms))
  1222.                         d2x_preferred = preferred = &PlayerCfg.HighestLevels[2];
  1223. #endif
  1224.                 else
  1225.                         continue;
  1226.                 if (preferred == &m)
  1227.                         continue;
  1228.                 std::swap(*preferred, m);
  1229.         }
  1230.         const unsigned reserved_slots = !!d1_preferred
  1231. #if defined(DXX_BUILD_DESCENT_II)
  1232.                 + !!d2_preferred + !!d2x_preferred
  1233. #endif
  1234.                 ;
  1235.         auto &hl = PlayerCfg.HighestLevels;
  1236.         const auto irs = &hl[reserved_slots];
  1237.  
  1238.         uint8_t previous_best_levelnum = 0;
  1239.         if (ii == ie)
  1240.         {
  1241.                 /*
  1242.                  * If ii == ie, the mission is unknown.  If there is no free
  1243.                  * space available, move everything, so that the
  1244.                  * least-recently-used element (at *irs) is discarded to make
  1245.                  * room to add this mission as most recently used.
  1246.                  *
  1247.                  * Leave previous_best_levelnum set to 0, so that any progress
  1248.                  * at all qualifies for a save.
  1249.                  */
  1250.                 if (ie == PlayerCfg.HighestLevels.end())
  1251.                 {
  1252.                         std::move(std::next(irs), ie, irs);
  1253.                         --ii;
  1254.                 }
  1255.                 else
  1256.                         PlayerCfg.NHighestLevels++;
  1257.                 ii->Shortname.back() = 0;
  1258.                 strncpy(ii->Shortname.data(), &*Current_mission->filename, ii->Shortname.size() - 1);
  1259.         }
  1260.         else if (ii != ie - 1)
  1261.         {
  1262.                 /* If this mission is not the most recently used, reorder the
  1263.                  * list so that it becomes the most recently used.
  1264.                  *
  1265.                  * Leave previous_best_levelnum set to 0, so that a save is
  1266.                  * required, even if the player has not set a new record.
  1267.                  */
  1268.                 std::rotate(ii, std::next(ii), ie);
  1269.                 ii = ie - 1;
  1270.         }
  1271.         else
  1272.                 /* Update previous_best_levelnum so that progress is only saved
  1273.                  * if this is a new best.
  1274.                  */
  1275.                 previous_best_levelnum = ii->LevelNum;
  1276.  
  1277.         if (previous_best_levelnum < best_levelnum_this_game)
  1278.         {
  1279.                 auto &best_levelnum_recorded = ii->LevelNum;
  1280.                 if (best_levelnum_recorded < best_levelnum_this_game)
  1281.                         best_levelnum_recorded = best_levelnum_this_game;
  1282.                 write_player_file();
  1283.         }
  1284. }
  1285.  
  1286. //gets the player's highest level from the file for this mission
  1287. int get_highest_level(void)
  1288. {
  1289.         read_player_file();
  1290.         const Mission_path &mission = *Current_mission;
  1291.         // Destination Saturn.
  1292.         const auto &&r = partial_range(PlayerCfg.HighestLevels, PlayerCfg.NHighestLevels);
  1293. #if defined(DXX_BUILD_DESCENT_I)
  1294.         unsigned highest_saturn_level = 0;
  1295.         if (!*mission.filename)
  1296.                 range_for (auto &m, r)
  1297.                         if (!d_stricmp("DESTSAT", m.Shortname.data()))
  1298.                                 highest_saturn_level = m.LevelNum;
  1299. #endif
  1300.         const auto &&i = find_hli_entry(r, mission);
  1301.         const auto ie = i[1];
  1302.         const auto ii = i[0];
  1303.         if (ii == ie)
  1304.                 return 0;
  1305.         auto LevelNum = ii->LevelNum;
  1306. #if defined(DXX_BUILD_DESCENT_I)
  1307.         /* Override `LevelNum` of the full campaign with the player's
  1308.          * progress in the OEM mini-campaign, so that users who switch can
  1309.          * keep their progress.
  1310.          *
  1311.          * If the player never played Destination Saturn, or if this is not
  1312.          * the built-in campaign, highest_saturn_level will be 0 and this
  1313.          * will be skipped.
  1314.          */
  1315.         if (LevelNum < highest_saturn_level)
  1316.                 LevelNum = highest_saturn_level;
  1317. #endif
  1318.         return LevelNum;
  1319. }
  1320.  
  1321. //write out player's saved games.  returns errno (0 == no error)
  1322. namespace dsx {
  1323. void write_player_file()
  1324. {
  1325.         char filename[PATH_MAX];
  1326.         int errno_ret;
  1327.  
  1328.         if ( Newdemo_state == ND_STATE_PLAYBACK )
  1329.                 return;
  1330.  
  1331.         errno_ret = WriteConfigFile();
  1332.  
  1333.         snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.plx"), static_cast<const char *>(InterfaceUniqueState.PilotName));
  1334.         write_player_dxx(filename);
  1335.         snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.plr"), static_cast<const char *>(InterfaceUniqueState.PilotName));
  1336.         auto file = PHYSFSX_openWriteBuffered(filename);
  1337.         if (!file)
  1338.                 return;
  1339.  
  1340.         //Write out player's info
  1341.         PHYSFS_writeULE32(file, SAVE_FILE_ID);
  1342. #if defined(DXX_BUILD_DESCENT_I)
  1343.         PHYSFS_writeULE16(file, SAVED_GAME_VERSION);
  1344.         PHYSFS_writeULE16(file, PLAYER_STRUCT_VERSION);
  1345.         PHYSFS_writeSLE32(file, PlayerCfg.NHighestLevels);
  1346.         PHYSFS_writeSLE32(file, PlayerCfg.DefaultDifficulty);
  1347.         PHYSFS_writeSLE32(file, PlayerCfg.AutoLeveling);
  1348.         errno_ret = EZERO;
  1349.  
  1350.         //write higest level info
  1351.         if ((PHYSFS_write( file, PlayerCfg.HighestLevels, sizeof(hli), PlayerCfg.NHighestLevels) != PlayerCfg.NHighestLevels)) {
  1352.                 errno_ret = errno;
  1353.                 return;
  1354.         }
  1355.  
  1356.         if (PHYSFS_write( file, saved_games,sizeof(saved_games),1) != 1) {
  1357.                 errno_ret = errno;
  1358.                 return;
  1359.         }
  1360.  
  1361.         range_for (auto &i, PlayerCfg.NetworkMessageMacro)
  1362.                 if (PHYSFS_write(file, i.data(), i.size(), 1) != 1) {
  1363.                 errno_ret = errno;
  1364.                 return;
  1365.         }
  1366.  
  1367.         //write kconfig info
  1368.         {
  1369.                 if (PHYSFS_write(file, PlayerCfg.KeySettings.Keyboard, sizeof(PlayerCfg.KeySettings.Keyboard), 1) != 1)
  1370.                         errno_ret=errno;
  1371. #if DXX_MAX_JOYSTICKS
  1372.                 auto &KeySettingsJoystick = PlayerCfg.KeySettings.Joystick;
  1373. #else
  1374.                 const std::array<uint8_t, MAX_CONTROLS> KeySettingsJoystick{};
  1375. #endif
  1376.                 if (PHYSFS_write(file, KeySettingsJoystick, sizeof(KeySettingsJoystick), 1) != 1)
  1377.                         errno_ret=errno;
  1378.                 for (unsigned i = 0; i < MAX_CONTROLS*3; i++)
  1379.                         if (PHYSFS_write(file, "0", sizeof(ubyte), 1) != 1) // Skip obsolete Flightstick/Thrustmaster/Gravis map fields
  1380.                                 errno_ret=errno;
  1381.                 if (PHYSFS_write(file, PlayerCfg.KeySettings.Mouse, sizeof(PlayerCfg.KeySettings.Mouse), 1) != 1)
  1382.                         errno_ret=errno;
  1383.                 {
  1384.                         std::array<uint8_t, MAX_CONTROLS> cyberman{};
  1385.                         if (PHYSFS_write(file, cyberman.data(), cyberman.size(), 1) != 1)       // Skip obsolete Cyberman map field
  1386.                                 errno_ret=errno;
  1387.                 }
  1388.        
  1389.                 if(errno_ret == EZERO)
  1390.                 {
  1391.                         ubyte old_avg_joy_sensitivity = 8;
  1392.                         if (PHYSFS_write( file,  &PlayerCfg.ControlType, sizeof(ubyte), 1 )!=1)
  1393.                                 errno_ret=errno;
  1394.                         else if (PHYSFS_write( file, &old_avg_joy_sensitivity, sizeof(ubyte), 1 )!=1)
  1395.                                 errno_ret=errno;
  1396.                 }
  1397.         }
  1398.  
  1399.         if (!file.close())
  1400.                 errno_ret = errno;
  1401.  
  1402.         if (errno_ret != EZERO) {
  1403.                 PHYSFS_delete(filename);                        //delete bogus file
  1404.                 nm_messagebox(TXT_ERROR, 1, TXT_OK, "%s\n\n%s",TXT_ERROR_WRITING_PLR, strerror(errno_ret));
  1405.         }
  1406. #elif defined(DXX_BUILD_DESCENT_II)
  1407.         (void)errno_ret;
  1408.         PHYSFS_writeULE16(file, PLAYER_FILE_VERSION);
  1409.  
  1410.        
  1411.         PHYSFS_seek(file,PHYSFS_tell(file)+2*(sizeof(PHYSFS_uint16))); // skip Game_window_w, Game_window_h
  1412.         PHYSFSX_writeU8(file, PlayerCfg.DefaultDifficulty);
  1413.         PHYSFSX_writeU8(file, PlayerCfg.AutoLeveling);
  1414.         PHYSFSX_writeU8(file, PlayerCfg.ReticleType==RET_TYPE_NONE?0:1);
  1415.         PHYSFSX_writeU8(file, PlayerCfg.CockpitMode[0]);
  1416.         PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(PHYSFS_uint8)); // skip Default_display_mode
  1417.         PHYSFSX_writeU8(file, static_cast<uint8_t>(PlayerCfg.MissileViewEnabled));
  1418.         PHYSFSX_writeU8(file, PlayerCfg.HeadlightActiveDefault);
  1419.         PHYSFSX_writeU8(file, PlayerCfg.GuidedInBigWindow);
  1420.         PHYSFS_seek(file,PHYSFS_tell(file)+sizeof(PHYSFS_uint8)); // skip Automap_always_hires
  1421.  
  1422.         //write higest level info
  1423.         PHYSFS_writeULE16(file, PlayerCfg.NHighestLevels);
  1424.         if ((PHYSFS_write(file, PlayerCfg.HighestLevels, sizeof(hli), PlayerCfg.NHighestLevels) != PlayerCfg.NHighestLevels))
  1425.                 goto write_player_file_failed;
  1426.  
  1427.         range_for (auto &i, PlayerCfg.NetworkMessageMacro)
  1428.                 if (PHYSFS_write(file, i.data(), i.size(), 1) != 1)
  1429.                 goto write_player_file_failed;
  1430.  
  1431.         //write kconfig info
  1432.         {
  1433.  
  1434.                 ubyte old_avg_joy_sensitivity = 8;
  1435.                 ubyte control_type_dos = PlayerCfg.ControlType;
  1436.  
  1437.                 if (PHYSFS_write(file, PlayerCfg.KeySettings.Keyboard, sizeof(PlayerCfg.KeySettings.Keyboard), 1) != 1)
  1438.                         goto write_player_file_failed;
  1439. #if DXX_MAX_JOYSTICKS
  1440.                 auto &KeySettingsJoystick = PlayerCfg.KeySettings.Joystick;
  1441. #else
  1442.                 const std::array<uint8_t, MAX_CONTROLS> KeySettingsJoystick{};
  1443. #endif
  1444.                 if (PHYSFS_write(file, KeySettingsJoystick, sizeof(KeySettingsJoystick), 1) != 1)
  1445.                         goto write_player_file_failed;
  1446.                 for (unsigned i = 0; i < MAX_CONTROLS*3; i++)
  1447.                         if (PHYSFS_write(file, "0", sizeof(ubyte), 1) != 1) // Skip obsolete Flightstick/Thrustmaster/Gravis map fields
  1448.                                 goto write_player_file_failed;
  1449.                 if (PHYSFS_write(file, PlayerCfg.KeySettings.Mouse, sizeof(PlayerCfg.KeySettings.Mouse), 1) != 1)
  1450.                         goto write_player_file_failed;
  1451.                 for (unsigned i = 0; i < MAX_CONTROLS*2; i++)
  1452.                         if (PHYSFS_write(file, "0", sizeof(ubyte), 1) != 1) // Skip obsolete Cyberman/Winjoy map fields
  1453.                                 goto write_player_file_failed;
  1454.                 if (PHYSFS_write(file, &control_type_dos, sizeof(ubyte), 1) != 1)
  1455.                         goto write_player_file_failed;
  1456.                 ubyte control_type_win = 0;
  1457.                 if (PHYSFS_write(file, &control_type_win, sizeof(ubyte), 1) != 1)
  1458.                         goto write_player_file_failed;
  1459.                 if (PHYSFS_write(file, &old_avg_joy_sensitivity, sizeof(ubyte), 1) != 1)
  1460.                         goto write_player_file_failed;
  1461.  
  1462.                 range_for (const unsigned i, xrange(11u))
  1463.                 {
  1464.                         PHYSFS_write(file, &PlayerCfg.PrimaryOrder[i], sizeof(ubyte), 1);
  1465.                         PHYSFS_write(file, &PlayerCfg.SecondaryOrder[i], sizeof(ubyte), 1);
  1466.                 }
  1467.  
  1468.                 PHYSFS_writeULE32(file, PlayerCfg.Cockpit3DView[0]);
  1469.                 PHYSFS_writeULE32(file, PlayerCfg.Cockpit3DView[1]);
  1470.  
  1471.                 PHYSFS_writeULE32(file, PlayerCfg.NetlifeKills);
  1472.                 PHYSFS_writeULE32(file, PlayerCfg.NetlifeKilled);
  1473.                 int i=get_lifetime_checksum (PlayerCfg.NetlifeKills,PlayerCfg.NetlifeKilled);
  1474.                 PHYSFS_writeULE32(file, i);
  1475.         }
  1476.  
  1477.         //write guidebot name
  1478.         PHYSFSX_writeString(file, PlayerCfg.GuidebotNameReal);
  1479.  
  1480.         {
  1481.                 char buf[128];
  1482.                 strcpy(buf, "DOS joystick");
  1483.                 PHYSFSX_writeString(file, buf);         // Write out current joystick for player.
  1484.         }
  1485.  
  1486.         if (!file.close())
  1487.                 goto write_player_file_failed;
  1488.  
  1489.         return;
  1490.  
  1491.  write_player_file_failed:
  1492.         nm_messagebox(TXT_ERROR, 1, TXT_OK, "%s\n\n%s", TXT_ERROR_WRITING_PLR, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
  1493.         if (file)
  1494.         {
  1495.                 file.reset();
  1496.                 PHYSFS_delete(filename);        //delete bogus file
  1497.         }
  1498. #endif
  1499. }
  1500.  
  1501. #if defined(DXX_BUILD_DESCENT_II)
  1502. static int get_lifetime_checksum (int a,int b)
  1503. {
  1504.   int num;
  1505.  
  1506.   // confusing enough to beat amateur disassemblers? Lets hope so
  1507.  
  1508.   num=(a<<8 ^ b);
  1509.   num^=(a | b);
  1510.   num*=num>>2;
  1511.   return (num);
  1512. }
  1513. #endif
  1514.  
  1515. template <uint_fast32_t shift, uint_fast32_t width>
  1516. static void convert_duplicate_powerup_integer(packed_netduplicate_items &d, const char *value)
  1517. {
  1518.         /* Initialize 'i' to avoid bogus -Wmaybe-uninitialized at -Og on
  1519.          * gcc-4.9 */
  1520.         unsigned i = 0;
  1521.         if (convert_integer(i, value) && !(i & ~((1 << width) - 1)))
  1522.                 d.set_sub_field<shift, width>(i);
  1523. }
  1524.  
  1525. // read stored values from ngp file to netgame_info
  1526. void read_netgame_profile(netgame_info *ng)
  1527. {
  1528.         char filename[PATH_MAX];
  1529. #if DXX_USE_TRACKER
  1530.         ng->TrackerNATWarned = TrackerNATHolePunchWarn::Unset;
  1531. #endif
  1532.  
  1533.         snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.ngp"), static_cast<const char *>(InterfaceUniqueState.PilotName));
  1534.         auto file = PHYSFSX_openReadBuffered(filename);
  1535.         if (!file)
  1536.                 return;
  1537.  
  1538.         ng->MPGameplayOptions.AutosaveInterval = std::chrono::minutes(10);
  1539.         // NOTE that we do not set any defaults here or even initialize netgame_info. For flexibility, leave that to the function calling this.
  1540.         for (PHYSFSX_gets_line_t<50> line; const char *const eol = PHYSFSX_fgets(line, file);)
  1541.         {
  1542.                 const auto lb = line.begin();
  1543.                 if (eol == line.end())
  1544.                         continue;
  1545.                 auto eq = std::find(lb, eol, '=');
  1546.                 if (eq == eol)
  1547.                         continue;
  1548.                 auto value = std::next(eq);
  1549.                 if (cmp(lb, eq, GameNameStr))
  1550.                         convert_string(ng->game_name, value, eol);
  1551.                 else if (cmp(lb, eq, GameModeStr))
  1552.                         convert_integer(ng->gamemode, value);
  1553.                 else if (cmp(lb, eq, RefusePlayersStr))
  1554.                         convert_integer(ng->RefusePlayers, value);
  1555.                 else if (cmp(lb, eq, DifficultyStr))
  1556.                 {
  1557.                         uint8_t difficulty;
  1558.                         if (convert_integer(difficulty, value))
  1559.                                 ng->difficulty = cast_clamp_difficulty(difficulty);
  1560.                 }
  1561.                 else if (cmp(lb, eq, GameFlagsStr))
  1562.                 {
  1563.                         packed_game_flags p;
  1564.                         if (convert_integer(p.value, value))
  1565.                                 ng->game_flag = unpack_game_flags(&p);
  1566.                 }
  1567.                 else if (cmp(lb, eq, AllowedItemsStr))
  1568.                         convert_integer(ng->AllowedItems, value);
  1569.                 else if (cmp(lb, eq, SpawnGrantedItemsStr))
  1570.                         convert_integer(ng->SpawnGrantedItems.mask, value);
  1571.                 else if (cmp(lb, eq, DuplicatePrimariesStr))
  1572.                         convert_duplicate_powerup_integer<packed_netduplicate_items::primary_shift, packed_netduplicate_items::primary_width>(ng->DuplicatePowerups, value);
  1573.                 else if (cmp(lb, eq, DuplicateSecondariesStr))
  1574.                         convert_duplicate_powerup_integer<packed_netduplicate_items::secondary_shift, packed_netduplicate_items::secondary_width>(ng->DuplicatePowerups, value);
  1575. #if defined(DXX_BUILD_DESCENT_II)
  1576.                 else if (cmp(lb, eq, DuplicateAccessoriesStr))
  1577.                         convert_duplicate_powerup_integer<packed_netduplicate_items::accessory_shift, packed_netduplicate_items::accessory_width>(ng->DuplicatePowerups, value);
  1578.                 else if (cmp(lb, eq, AllowMarkerViewStr))
  1579.                         convert_integer(ng->Allow_marker_view, value);
  1580.                 else if (cmp(lb, eq, AlwaysLightingStr))
  1581.                         convert_integer(ng->AlwaysLighting, value);
  1582.                 else if (cmp(lb, eq, ThiefAbsenceFlagStr))
  1583.                 {
  1584.                         if (strtoul(value, 0, 10))
  1585.                                 ng->ThiefModifierFlags |= ThiefModifier::Absent;
  1586.                 }
  1587.                 else if (cmp(lb, eq, ThiefNoEnergyWeaponsFlagStr))
  1588.                 {
  1589.                         if (strtoul(value, 0, 10))
  1590.                                 ng->ThiefModifierFlags |= ThiefModifier::NoEnergyWeapons;
  1591.                 }
  1592.                 else if (cmp(lb, eq, AllowGuidebotStr))
  1593.                         convert_integer(ng->AllowGuidebot, value);
  1594. #endif
  1595.                 else if (cmp(lb, eq, ShufflePowerupsStr))
  1596.                         convert_integer(ng->ShufflePowerupSeed, value);
  1597.                 else if (cmp(lb, eq, ShowEnemyNamesStr))
  1598.                         convert_integer(ng->ShowEnemyNames, value);
  1599.                 else if (cmp(lb, eq, BrightPlayersStr))
  1600.                         convert_integer(ng->BrightPlayers, value);
  1601.                 else if (cmp(lb, eq, InvulAppearStr))
  1602.                         convert_integer(ng->InvulAppear, value);
  1603.                 else if (cmp(lb, eq, KillGoalStr))
  1604.                         convert_integer(ng->KillGoal, value);
  1605.                 else if (cmp(lb, eq, PlayTimeAllowedStr))
  1606.                 {
  1607.                         int PlayTimeAllowed;
  1608.                         if (convert_integer(PlayTimeAllowed, value))
  1609.                                 ng->PlayTimeAllowed = std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>(PlayTimeAllowed);
  1610.                 }
  1611.                 else if (cmp(lb, eq, ControlInvulTimeStr))
  1612.                         convert_integer(ng->control_invul_time, value);
  1613.                 else if (cmp(lb, eq, PacketsPerSecStr))
  1614.                         convert_integer(ng->PacketsPerSec, value);
  1615.                 else if (cmp(lb, eq, NoFriendlyFireStr))
  1616.                         convert_integer(ng->NoFriendlyFire, value);
  1617.                 else if (cmp(lb, eq, MouselookFlagsStr))
  1618.                         convert_integer(ng->MouselookFlags, value);
  1619.                 else if (cmp(lb, eq, AutosaveIntervalStr))
  1620.                 {
  1621.                         uint16_t AutosaveInterval;
  1622.                         if (convert_integer(AutosaveInterval, value))
  1623.                                 ng->MPGameplayOptions.AutosaveInterval = std::chrono::seconds(AutosaveInterval);
  1624.                 }
  1625. #if DXX_USE_TRACKER
  1626.                 else if (cmp(lb, eq, TrackerStr))
  1627.                         convert_integer(ng->Tracker, value);
  1628.                 else if (cmp(lb, eq, TrackerNATHPStr))
  1629.                         ng->TrackerNATWarned = static_cast<TrackerNATHolePunchWarn>(strtoul(value, 0, 10));
  1630. #endif
  1631.         }
  1632. }
  1633.  
  1634. // write values from netgame_info to ngp file
  1635. void write_netgame_profile(netgame_info *ng)
  1636. {
  1637.         char filename[PATH_MAX];
  1638.         snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%.8s.ngp"), static_cast<const char *>(InterfaceUniqueState.PilotName));
  1639.         auto file = PHYSFSX_openWriteBuffered(filename);
  1640.         if (!file)
  1641.                 return;
  1642.  
  1643.         PHYSFSX_printf(file, GameNameStr "=%s\n", ng->game_name.data());
  1644.         PHYSFSX_printf(file, GameModeStr "=%i\n", ng->gamemode);
  1645.         PHYSFSX_printf(file, RefusePlayersStr "=%i\n", ng->RefusePlayers);
  1646.         PHYSFSX_printf(file, DifficultyStr "=%i\n", ng->difficulty);
  1647.         PHYSFSX_printf(file, GameFlagsStr "=%i\n", pack_game_flags(&ng->game_flag).value);
  1648.         PHYSFSX_printf(file, AllowedItemsStr "=%i\n", ng->AllowedItems);
  1649.         PHYSFSX_printf(file, SpawnGrantedItemsStr "=%i\n", ng->SpawnGrantedItems.mask);
  1650.         PHYSFSX_printf(file, DuplicatePrimariesStr "=%" PRIuFAST32 "\n", ng->DuplicatePowerups.get_primary_count());
  1651.         PHYSFSX_printf(file, DuplicateSecondariesStr "=%" PRIuFAST32 "\n", ng->DuplicatePowerups.get_secondary_count());
  1652. #if defined(DXX_BUILD_DESCENT_II)
  1653.         PHYSFSX_printf(file, DuplicateAccessoriesStr "=%" PRIuFAST32 "\n", ng->DuplicatePowerups.get_accessory_count());
  1654.         PHYSFSX_printf(file, AllowMarkerViewStr "=%i\n", ng->Allow_marker_view);
  1655.         PHYSFSX_printf(file, AlwaysLightingStr "=%i\n", ng->AlwaysLighting);
  1656.         PHYSFSX_printf(file, ThiefAbsenceFlagStr "=%i\n", ng->ThiefModifierFlags & ThiefModifier::Absent);
  1657.         PHYSFSX_printf(file, ThiefNoEnergyWeaponsFlagStr "=%i\n", ng->ThiefModifierFlags & ThiefModifier::NoEnergyWeapons);
  1658.         PHYSFSX_printf(file, AllowGuidebotStr "=%i\n", ng->AllowGuidebot);
  1659. #endif
  1660.         PHYSFSX_printf(file, ShufflePowerupsStr "=%i\n", !!ng->ShufflePowerupSeed);
  1661.         PHYSFSX_printf(file, ShowEnemyNamesStr "=%i\n", ng->ShowEnemyNames);
  1662.         PHYSFSX_printf(file, BrightPlayersStr "=%i\n", ng->BrightPlayers);
  1663.         PHYSFSX_printf(file, InvulAppearStr "=%i\n", ng->InvulAppear);
  1664.         PHYSFSX_printf(file, KillGoalStr "=%i\n", ng->KillGoal);
  1665.         PHYSFSX_printf(file, PlayTimeAllowedStr "=%i\n", std::chrono::duration_cast<std::chrono::duration<int, netgame_info::play_time_allowed_abi_ratio>>(ng->PlayTimeAllowed).count());
  1666.         PHYSFSX_printf(file, ControlInvulTimeStr "=%i\n", ng->control_invul_time);
  1667.         PHYSFSX_printf(file, PacketsPerSecStr "=%i\n", ng->PacketsPerSec);
  1668.         PHYSFSX_printf(file, NoFriendlyFireStr "=%i\n", ng->NoFriendlyFire);
  1669.         PHYSFSX_printf(file, MouselookFlagsStr "=%i\n", ng->MouselookFlags);
  1670.         PHYSFSX_printf(file, AutosaveIntervalStr "=%i\n", ng->MPGameplayOptions.AutosaveInterval.count());
  1671. #if DXX_USE_TRACKER
  1672.         PHYSFSX_printf(file, TrackerStr "=%i\n", ng->Tracker);
  1673.         PHYSFSX_printf(file, TrackerNATHPStr "=%i\n", ng->TrackerNATWarned);
  1674. #else
  1675.         PHYSFSX_puts_literal(file, TrackerStr "=0\n" TrackerNATHPStr "=0\n");
  1676. #endif
  1677.         PHYSFSX_printf(file, NGPVersionStr "=" DXX_VERSION_STR "\n");
  1678. }
  1679.  
  1680. }
  1681.