Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * Portions of this file are copyright Rebirth contributors and licensed as
3
 * described in COPYING.txt.
4
 * Portions of this file are copyright Parallax Software and licensed
5
 * according to the Parallax license below.
6
 * See COPYING.txt for license details.
7
 
8
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18
*/
19
 
20
/*
21
 *
22
 * Functions to 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
}