]> icculus.org git repositories - taylor/freespace2.git/blob - src/playerman/managepilot.cpp
sync files whenever pilot file is saved
[taylor/freespace2.git] / src / playerman / managepilot.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/Playerman/ManagePilot.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * ManagePilot.cpp has code to load and save pilot files, and to select and 
16  * manage the pilot
17  *
18  * $Log$
19  * Revision 1.6  2004/07/04 11:41:24  taylor
20  * warning fixes, cross platform pilot compat with OSX-Linux-Windows
21  *
22  * Revision 1.5  2003/05/25 02:30:43  taylor
23  * Freespace 1 support
24  *
25  * Revision 1.4  2002/06/09 04:41:25  relnev
26  * added copyright header
27  *
28  * Revision 1.3  2002/06/09 03:16:05  relnev
29  * added _splitpath.
30  *
31  * removed unneeded asm, old sdl 2d setup.
32  *
33  * fixed crash caused by opengl_get_region.
34  *
35  * Revision 1.2  2002/05/07 03:16:50  theoddone33
36  * The Great Newline Fix
37  *
38  * Revision 1.1.1.1  2002/05/03 03:28:11  root
39  * Initial import.
40  *
41  * 
42  * 41    9/13/99 4:52p Dave
43  * RESPAWN FIX
44  * 
45  * 40    9/01/99 10:09a Dave
46  * Pirate bob.
47  * 
48  * 39    8/26/99 8:49p Jefff
49  * old player file compatibility with new medal stuff
50  * 
51  * 38    8/22/99 5:53p Dave
52  * Scoring fixes. Added self destruct key. Put callsigns in the logfile
53  * instead of ship designations for multiplayer players.
54  * 
55  * 37    8/16/99 4:06p Dave
56  * Big honking checkin.
57  * 
58  * 36    8/11/99 11:36a Jefff
59  * added compatibility w/ fs2 demo plr version
60  * 
61  * 35    8/10/99 3:46p Jefff
62  * changes for Intelligence section of new tech room
63  * 
64  * 34    8/04/99 11:38p Andsager
65  * make new pilot detail level match registry info.
66  * 
67  * 33    8/02/99 9:55p Dave
68  * Hardcode a nice hud color config for the demo.
69  * 
70  * 32    8/02/99 9:13p Dave
71  * Added popup tips.
72  * 
73  * 31    8/01/99 12:39p Dave
74  * Added HUD contrast control key (for nebula).
75  * 
76  * 30    7/30/99 10:31p Dave
77  * Added comm menu to the configurable hud files.
78  * 
79  * 29    7/29/99 10:47p Dave
80  * Standardized D3D fogging using vertex fog. Shook out Savage 4 bugs.
81  * 
82  * 28    7/29/99 12:05a Dave
83  * Nebula speed optimizations.
84  * 
85  * 27    7/24/99 1:54p Dave
86  * Hud text flash gauge. Reworked dead popup to use 4 buttons in red-alert
87  * missions.
88  * 
89  * 26    6/22/99 7:03p Dave
90  * New detail options screen.
91  * 
92  * 25    6/16/99 4:06p Dave
93  * New pilot info popup. Added new draw-bitmap-as-poly function.
94  * 
95  * 24    6/11/99 11:13a Dave
96  * last minute changes before press tour build.
97  * 
98  * 23    6/08/99 1:14a Dave
99  * Multi colored hud test.
100  * 
101  * 22    5/03/99 8:33p Dave
102  * New version of multi host options screen.
103  * 
104  * 21    3/24/99 4:05p Dave
105  * Put in support for assigning the player to a specific squadron with a
106  * specific logo. Preliminary work for doing pos/orient checksumming in
107  * multiplayer to reduce bandwidth.
108  * 
109  * 20    1/30/99 1:29a Dave
110  * Fixed nebula thumbnail problem. Full support for 1024x768 choose pilot
111  * screen.  Fixed beam weapon death messages.
112  * 
113  * 19    1/29/99 2:08a Dave
114  * Fixed beam weapon collisions with players. Reduced size of scoring
115  * struct for multiplayer. Disabled PXO.
116  * 
117  * 18    1/21/99 2:06p Dave
118  * Final checkin for multiplayer testing.
119  * 
120  * 17    1/15/99 2:49p Dave
121  * Fixed creation of pilots.
122  * 
123  * 16    1/14/99 6:06p Dave
124  * 100% full squad logo support for single player and multiplayer.
125  * 
126  * 15    1/12/99 3:15a Dave
127  * Barracks screen support for selecting squad logos. We need real artwork
128  * :)
129  * 
130  * 14    1/06/99 2:24p Dave
131  * Stubs and release build fixes.
132  * 
133  * 13    12/14/98 12:13p Dave
134  * Spiffed up xfer system a bit. Put in support for squad logo file xfer.
135  * Need to test now.
136  * 
137  * 12    11/20/98 11:16a Dave
138  * Fixed up IPX support a bit. Making sure that switching modes and
139  * loading/saving pilot files maintains proper state.
140  * 
141  * 11    11/19/98 4:51p Dave
142  * Ignore multiplayer protocol settings in the pilot file for now.
143  * 
144  * 10    11/19/98 4:19p Dave
145  * Put IPX sockets back in psnet. Consolidated all multiplayer config
146  * files into one.
147  * 
148  * 9     10/13/98 2:47p Andsager
149  * Remove reference to Tech_shivan_species_avail
150  * 
151  * 8     10/13/98 9:29a Dave
152  * Started neatening up freespace.h. Many variables renamed and
153  * reorganized. Added AlphaColors.[h,cpp]
154  * 
155  * 7     10/12/98 9:30a Andsager
156  * Clean up barracks.cpp.  Remove unneeded ".h" files from ManagePilot
157  * 
158  * 6     10/09/98 5:17p Andsager
159  * move barracks screen into barracks.cpp
160  * 
161  * 5     10/09/98 2:57p Dave
162  * Starting splitting up OS stuff.
163  * 
164  * 4     10/08/98 9:19a Andsager
165  * Clean up pilot player read and write, starting with new version.
166  * 
167  * 3     10/07/98 6:27p Dave
168  * Globalized mission and campaign file extensions. Removed Silent Threat
169  * special code. Moved \cache \players and \multidata into the \data
170  * directory.
171  * 
172  * 2     10/07/98 10:53a Dave
173  * Initial checkin.
174  * 
175  * 1     10/07/98 10:50a Dave
176  * 
177  * 279   9/21/98 10:02p Dave
178  * Last minute changes to techroom weapon/ship/species stuff.
179  * 
180  * 278   9/08/98 12:10p Andsager
181  * Fixed a bug with saving ship and weapon info techroom flags.
182  * 
183  * 277   9/01/98 4:25p Dave
184  * Put in total (I think) backwards compatibility between mission disk
185  * freespace and non mission disk freespace, including pilot files and
186  * campaign savefiles.
187  * 
188  * 276   6/09/98 10:31a Hoffoss
189  * Created index numbers for all xstr() references.  Any new xstr() stuff
190  * added from here on out should be added to the end if the list.  The
191  * current list count can be found in FreeSpace.cpp (search for
192  * XSTR_SIZE).
193  * 
194  * 275   6/05/98 9:49a Lawrance
195  * OEM changes
196  * 
197  * 274   6/01/98 11:43a John
198  * JAS & MK:  Classified all strings for localization.
199  * 
200  * 273   5/26/98 11:53a Allender
201  * fix multiplayer problems and sexpression crash
202  * 
203  * 272   5/24/98 2:46p Lawrance
204  * Fix bug where skill level would be reset to default when switching
205  * between pilots
206  * 
207  * 271   5/23/98 4:02p Allender
208  * version change
209  * 
210  * 270   5/23/98 2:41p Mike
211  * Make Easy the default skill level and prevent old pilot's skill level
212  * from carrying into new pilot.
213  *
214 */
215
216 #include <errno.h>
217 #include "managepilot.h"
218 #include "2d.h"
219 #include "freespace.h"
220 #include "hudsquadmsg.h"
221 #include "sound.h"
222 #include "multi.h"
223 #include "eventmusic.h"
224 #include "audiostr.h"
225 #include "osregistry.h"
226 #include "font.h"
227 #include "playermenu.h"
228 #include "missionshipchoice.h"
229 #include "hudconfig.h"
230 #include "popup.h"
231 #include "redalert.h"
232 #include "techmenu.h"
233 #include "joy.h"
234 #include "mouse.h"
235 #include "cutscenes.h"
236 #include "bmpman.h"
237 #include "key.h"
238
239 // update this when altering data that is read/written to .PLR file
240 #if defined(FS2_DEMO)
241         #define CURRENT_PLAYER_FILE_VERSION                             136             // 1.10
242         #define PREVIOUS_PLAYER_FILE_VERSION                    135             // 1.00
243
244         #define LOWEST_COMPATIBLE_PLAYER_FILE_VERSION   PREVIOUS_PLAYER_FILE_VERSION
245 #elif defined(FS1_DEMO)
246         #define CURRENT_PLAYER_FILE_VERSION                             94              // 1.20
247         #define PREVIOUS_PLAYER_FILE_VERSION                    86              // 1.00
248
249         #define LOWEST_COMPATIBLE_PLAYER_FILE_VERSION   PREVIOUS_PLAYER_FILE_VERSION
250 #elif defined(MAKE_FS1)
251         #define CURRENT_PLAYER_FILE_VERSION                             100             // 1.04
252         #define PREVIOUS_PLAYER_FILE_VERSION                    99              // 1.00
253
254         #define LOWEST_COMPATIBLE_PLAYER_FILE_VERSION   PREVIOUS_PLAYER_FILE_VERSION
255 #else
256         #define CURRENT_PLAYER_FILE_VERSION                             140             // 1.00
257
258         #define LOWEST_COMPATIBLE_PLAYER_FILE_VERSION   CURRENT_PLAYER_FILE_VERSION                     // demo plr files should work in final
259 #endif
260
261 // keep track of pilot file changes here 
262 // version 2    : Added squad logo filename
263 // version 3    : Changed size of scoring struct. use ushort instead of ints for storing alltime kills by ship type
264 // version 4/5 : Added squadron name field
265 // version 6    : changed max length on a multiplayer options field
266 // version 130  : changed size of hud config struct
267 // version 133 : misc changes. new hud gauge
268 // version 134 : added HUD contrast toggle key
269 // version 135 : added tips flag  (THIS IS THE DEMO VERSION - RETAIN COMPATIBILITY FROM HERE ON OUT)
270 // version 136 : added intelligence flags to tech room visibility data
271 // version 137 : 2 new HUD gauges. 
272 // version 138  : new multiplayer config
273 // version 139 : # medals increased - added compatibility with old plr file versions
274 // version 140 : ships table reordered. clear out old pilot files
275 // search for PLAYER INIT for new pilot initialization stuff. I _think_ its in the right spot for now
276 #define PLR_FILE_ID     0x46505346      // FPSF, unique signiture to identify a .PLR file (FreeSpace Player File)  // FPSF appears as FSPF in file.
277
278 #ifdef MAKE_FS1
279 #define PLR_MAX_SHIP_TYPES_OLD                  75
280 #define PLR_MAX_WEAPON_TYPES_OLD                44
281
282 extern void hud_config_set_color(int color);
283 #endif
284
285 // Current content of a .PLR file
286 //
287
288 // Global variables
289 int Player_sel_mode;
290
291 // pilot pic image list stuff ( call pilot_load_pic_list() to make these valid )
292 char Pilot_images_arr[MAX_PILOT_IMAGES][MAX_FILENAME_LEN];
293 char *Pilot_image_names[MAX_PILOT_IMAGES];
294 int Num_pilot_images = 0;
295
296 // squad logo list stuff (call pilot_load_squad_pic_list() to make these valid )
297 char Pilot_squad_images_arr[MAX_PILOT_IMAGES][MAX_FILENAME_LEN];
298 char *Pilot_squad_image_names[MAX_PILOT_IMAGES];
299 int Num_pilot_squad_images = 0;
300
301 static uint Player_file_version;
302
303 // forward declarations
304 void read_detail_settings(CFILE *file, int Player_file_version);
305 void write_detail_settings(CFILE *file);
306 void read_stats_block(CFILE *file, int Player_file_version, scoring_struct *stats);
307 void write_stats_block(CFILE *file, scoring_struct *stats);
308 void read_multiplayer_options(player *p,CFILE *file);
309 void write_multiplayer_options(player *p,CFILE *file);
310
311 static SDL_Keycode keycode_translate_from(short keycode);
312 static short keycode_translate_to(SDL_Keycode keycode);
313
314 // internal function to delete a player file.  Called after a pilot is obsoleted, and when a pilot is deleted
315 // used in barracks and player_select
316 void delete_pilot_file( const char *pilot_name, int single )
317 {
318         char filename[MAX_FILENAME_LEN];
319         char basename[MAX_FILENAME_LEN];
320
321         // get the player file.
322
323         base_filename(pilot_name, basename, SDL_arraysize(basename));
324
325         SDL_strlcpy( filename, basename, SDL_arraysize(filename) );
326         SDL_strlcat(filename, NOX(".plr"), SDL_arraysize(filename) );
327         if (Player_sel_mode == PLAYER_SELECT_MODE_SINGLE){
328                 cf_delete(filename, CF_TYPE_SINGLE_PLAYERS);
329         } else {
330                 cf_delete(filename, CF_TYPE_MULTI_PLAYERS);
331         }
332
333         // we must try and delete the campaign save files for a pilot as well.
334         mission_campaign_delete_all_savefiles( basename, !single );
335 }
336
337 // check if a pilot file is valid or not (i.e. is usable, not out of date, etc)
338 // used in barracks and player_select
339 int verify_pilot_file(const char *filename, int single, int *rank)
340 {
341         CFILE   *file;
342         uint id, file_version;
343         int type;
344
345         filename = cf_add_ext(filename, NOX(".plr"));
346         
347         if (single){
348                 file = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
349         } else {
350                 file = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_MULTI_PLAYERS);
351         }
352
353         if (!file){
354                 return -1;
355         }
356
357         id = cfread_uint(file);
358         if (id != PLR_FILE_ID) {
359                 nprintf(("Warning", "Player file has invalid signature\n"));
360                 cfclose(file);
361                 delete_pilot_file( filename, single );
362                 return -1;
363         }
364
365         // check for compatibility here
366         file_version = cfread_uint(file);
367 /*      if (file_version < INITIAL_RELEASE_FILE_VERSION) { */
368 //      if (file_version != CURRENT_PLAYER_FILE_VERSION) {
369         if (file_version < LOWEST_COMPATIBLE_PLAYER_FILE_VERSION) {
370                 nprintf(("Warning", "WARNING => Player file is outdated and not compatible...\n"));
371                 cfclose(file);
372                 delete_pilot_file( filename, single );
373                 return -1;
374         }
375
376         type = !cfread_ubyte(file);
377         if (rank){
378                 *rank = 0;      
379         }
380                 
381         if (rank){
382                 *rank = cfread_int(file);
383         } else {
384                 cfread_int(file);
385         }
386
387         cfclose(file);
388         if (type != single){
389                 return -1;
390         }
391
392         return 0;
393 }
394
395 void pilot_write_techroom_data(CFILE *file)
396 {
397         int idx;
398
399 #ifdef FS1_DEMO
400         int flags;
401
402         // ships
403         flags = 0;
404
405         for (idx = 0; idx < Num_ship_types; idx++) {
406                 if (Ship_info[idx].flags & SIF_IN_TECH_DATABASE) {
407                         flags |= 1<<idx;
408                 }
409         }
410
411         cfwrite_int(flags, file);
412
413         // weapons - first set
414         flags = 0;
415
416         for (idx = 0; (idx < 32) && (idx < Num_weapon_types); idx++) {
417                 if (Weapon_info[idx].wi_flags & WIF_IN_TECH_DATABASE) {
418                         flags |= 1<<idx;
419                 }
420         }
421
422         cfwrite_int(flags, file);
423
424         // weapons - second set
425         flags = 0;
426
427         for (idx = 32; idx < Num_weapon_types; idx++) {
428                 if (Weapon_info[idx].wi_flags & WIF_IN_TECH_DATABASE) {
429                         flags |= 1<<(idx-32);
430                 }
431         }
432
433         cfwrite_int(flags, file);
434 #else
435         ubyte out;
436
437         // write the ship and weapon count
438         cfwrite_int(Num_ship_types, file);
439         cfwrite_int(Num_weapon_types, file);
440 #ifndef MAKE_FS1
441         cfwrite_int(Intel_info_size, file);
442 #endif
443
444         // write all ship flags out
445         for (idx=0; idx<Num_ship_types; idx++) {
446                 out = (Ship_info[idx].flags & SIF_IN_TECH_DATABASE) ? (ubyte)1 : (ubyte)0;              
447                 cfwrite_ubyte(out, file);                               
448         }
449
450         // write all weapon types out
451         for (idx=0; idx<Num_weapon_types; idx++) {
452                 out = (Weapon_info[idx].wi_flags & WIF_IN_TECH_DATABASE) ? (ubyte)1 : (ubyte)0;
453                 cfwrite_ubyte(out, file);
454         }
455 #endif
456
457 #ifndef MAKE_FS1
458         // write all intel entry flags out
459         for (idx=0; idx<Intel_info_size; idx++) {
460                 cfwrite_ubyte((ubyte)Intel_info[idx].in_tech_db, file);
461         }
462 #else
463         // whether shivans are visible or not
464         int shivans = Intel_info[2].in_tech_db ? 1 : 0;
465         cfwrite_int(shivans, file);
466 #endif
467 }
468
469 #ifdef MAKE_FS1
470 void pilot_read_techroom_data(CFILE *file)
471 {
472         int idx;
473         int ship_count, weapon_count, shivans;
474         ubyte in;
475
476         if (Player_file_version < 100) {
477                 int vflags = 0;
478
479                 // first set of ships visible
480                 vflags = cfread_int(file);
481
482                 for (idx = 0; idx < 32; idx++) {
483                         if ( (vflags & (1<<idx)) && (idx < Num_ship_types) ) {
484                                 Ship_info[idx].flags |= SIF_IN_TECH_DATABASE | SIF_IN_TECH_DATABASE_M;
485                         } else if (idx < Num_ship_types) {
486                                 Ship_info[idx].flags &= ~SIF_IN_TECH_DATABASE;
487                         }
488                 }
489
490 #ifndef FS1_DEMO
491                 // second set of ships visible
492                 vflags = cfread_int(file);
493
494                 for (idx = 0; idx < 32; idx++) {
495                         if ( (vflags & (1<<idx)) && ((idx+32) < Num_ship_types) ) {
496                                 Ship_info[idx+32].flags |= SIF_IN_TECH_DATABASE | SIF_IN_TECH_DATABASE_M;
497                         } else if ((idx+32) < Num_ship_types) {
498                                 Ship_info[idx+32].flags &= ~SIF_IN_TECH_DATABASE;
499                         }
500                 }
501
502                 // last set of ships visible
503                 vflags = cfread_int(file);
504
505                 for (idx = 0; idx < 32; idx++) {
506                         if ( (vflags & (1<<idx)) && ((idx+64) < Num_ship_types) ) {
507                                 Ship_info[idx+64].flags |= SIF_IN_TECH_DATABASE | SIF_IN_TECH_DATABASE_M;
508                         } else if ((idx+64) < Num_ship_types) {
509                                 Ship_info[idx+64].flags &= ~SIF_IN_TECH_DATABASE;
510                         }
511                 }
512 #endif
513
514                 // first set of weapons visible
515                 vflags = cfread_int(file);
516
517                 for (idx = 0; idx < 32; idx++) {
518                         if ( (vflags & (1<<idx)) && (idx < Num_weapon_types) ) {
519                                 Weapon_info[idx].wi_flags |= WIF_IN_TECH_DATABASE;
520                         } else if (idx < Num_weapon_types) {
521                                 Weapon_info[idx].wi_flags &= ~WIF_IN_TECH_DATABASE;
522                         }
523                 }
524
525                 // last set of weapons visible
526                 vflags = cfread_int(file);
527
528                 for (idx = 0; idx < 32; idx++) {
529                         if ( (vflags & (1<<idx)) && ((idx+32) < Num_weapon_types) ) {
530                                 Weapon_info[idx+32].wi_flags |= WIF_IN_TECH_DATABASE;
531                         } else if ((idx+32) < Num_weapon_types) {
532                                 Weapon_info[idx+32].wi_flags &= ~WIF_IN_TECH_DATABASE;
533                         }
534                 }
535
536                 // shivans visible?
537                 shivans = cfread_int(file);
538
539                 if (shivans) {
540                         Intel_info[2].in_tech_db = 1;
541                 } else {
542                         Intel_info[2].in_tech_db = 0;
543                 }
544         } else {
545                 // read in ship and weapon counts
546                 ship_count = cfread_int(file);
547                 weapon_count = cfread_int(file);
548                 SDL_assert(ship_count <= MAX_SHIP_TYPES);
549                 SDL_assert(weapon_count <= MAX_WEAPON_TYPES);
550
551                 // read all ships in
552                 for (idx=0; idx<ship_count; idx++) {
553                         in = cfread_ubyte(file);
554                         if (in) {
555                                 Ship_info[idx].flags |= SIF_IN_TECH_DATABASE | SIF_IN_TECH_DATABASE_M;
556                         } else {
557                                 Ship_info[idx].flags &= ~SIF_IN_TECH_DATABASE;
558                         }
559                 }
560
561                 // read all weapons in
562                 for (idx=0; idx<weapon_count; idx++) {
563                         in = cfread_ubyte(file);
564                         if (in) {
565                                 Weapon_info[idx].wi_flags |= WIF_IN_TECH_DATABASE;
566                         } else {
567                                 Weapon_info[idx].wi_flags &= ~WIF_IN_TECH_DATABASE;
568                         }
569                 }
570
571                 // shivans visible?
572                 shivans = cfread_int(file);
573
574                 if (shivans) {
575                         Intel_info[2].in_tech_db = 1;
576                 } else {
577                         Intel_info[2].in_tech_db = 0;
578                 }
579         }
580 }
581 #else
582 void pilot_read_techroom_data(CFILE *file)
583 {
584         int idx;
585         int ship_count, weapon_count, intel_count;
586         ubyte in;
587
588         // read in ship and weapon counts
589         ship_count = cfread_int(file);
590         weapon_count = cfread_int(file);
591         SDL_assert(ship_count <= MAX_SHIP_TYPES);
592         SDL_assert(weapon_count <= MAX_WEAPON_TYPES);
593
594         // maintain compatibility w/ demo version
595         if (Player_file_version < 136) {
596                 // skip over all this data, because the lack of tech room in the demo
597                 // left this all hosed in the demo .plr files
598                 // this will all get initialized as if this fella was a new pilot
599                 for (idx=0; idx<ship_count+weapon_count; idx++) {
600                         cfread_ubyte(file);
601                 }
602
603         } else {
604
605                 intel_count = cfread_int(file);
606                 SDL_assert(intel_count <= MAX_INTEL_ENTRIES);
607
608                 // read all ships in
609                 for (idx=0; idx<ship_count; idx++) {
610                         in = cfread_ubyte(file);
611                         if (in) {
612                                 Ship_info[idx].flags |= SIF_IN_TECH_DATABASE | SIF_IN_TECH_DATABASE_M;
613                         } else {
614                                 Ship_info[idx].flags &= ~SIF_IN_TECH_DATABASE;
615                         }
616                 }
617
618                 // read all weapons in
619                 for (idx=0; idx<weapon_count; idx++) {
620                         in = cfread_ubyte(file);
621                         if (in) {
622                                 Weapon_info[idx].wi_flags |= WIF_IN_TECH_DATABASE;
623                         } else {
624                                 Weapon_info[idx].wi_flags &= ~WIF_IN_TECH_DATABASE;
625                         }
626                 }
627
628                 // read all intel entries in
629                 for (idx=0; idx<intel_count; idx++) {
630                         in = cfread_ubyte(file);
631                         if (in) {
632                                 Intel_info[idx].in_tech_db = 1;
633                         } else {
634                                 Intel_info[idx].in_tech_db = 0;
635                         }
636                 }
637         }
638 }
639 #endif
640
641 // write out the player ship selection
642 void pilot_write_loadout(CFILE *file)
643 {
644         int i, j;
645         wss_unit *slot; 
646
647         cfwrite_string_len(Player_loadout.filename, file);
648         cfwrite_string_len(Player_loadout.last_modified, file);
649
650 #ifdef FS1_DEMO
651         // write ship pool
652         for ( i = 0; i < MAX_SHIP_TYPES; i++ ) {
653                 cfwrite_int(Player_loadout.ship_pool[i], file);
654         }
655
656         // write weapons pool
657         for ( i = 0; i < MAX_WEAPON_TYPES; i++ ) {
658                 cfwrite_int(Player_loadout.weapon_pool[i], file);
659         }
660 #else
661         // write ship and weapon counts
662         cfwrite_int(Num_ship_types, file);
663         cfwrite_int(Num_weapon_types, file);
664
665         // write ship pool
666         for ( i = 0; i < Num_ship_types; i++ ) {
667                 cfwrite_int(Player_loadout.ship_pool[i], file);
668         }
669
670         // write weapons pool
671         for ( i = 0; i < Num_weapon_types; i++ ) {
672                 cfwrite_int(Player_loadout.weapon_pool[i], file);
673         }
674 #endif
675
676         // write ship loadouts
677         for ( i = 0; i < MAX_WSS_SLOTS; i++ ) {
678                 slot = &Player_loadout.unit_data[i];
679                 cfwrite_int(slot->ship_class, file);
680                 for ( j = 0; j < MAX_WL_WEAPONS; j++ ) {
681                         cfwrite_int(slot->wep[j], file);
682                         cfwrite_int(slot->wep_count[j], file);
683                 }
684         }
685 }
686
687 // read in the ship selection for the pilot
688 void pilot_read_loadout(CFILE *file)
689 {
690         int i, j;
691         wss_unit *slot;
692         int ship_count, weapon_count;
693
694         memset(Player_loadout.filename, 0, MAX_FILENAME_LEN);
695         cfread_string_len(Player_loadout.filename, MAX_FILENAME_LEN, file);
696
697         memset(Player_loadout.last_modified, 0, DATE_TIME_LENGTH);      
698         cfread_string_len(Player_loadout.last_modified, DATE_TIME_LENGTH, file);        
699
700         // read in ship and weapon counts
701 #if defined(FS1_DEMO)
702         ship_count = MAX_SHIP_TYPES;
703         weapon_count = MAX_WEAPON_TYPES;
704 #elif defined(MAKE_FS1)
705         if (Player_file_version < 100) {
706                 ship_count = PLR_MAX_SHIP_TYPES_OLD;
707                 weapon_count = PLR_MAX_WEAPON_TYPES_OLD;
708         } else {
709                 ship_count = cfread_int(file);
710                 weapon_count = cfread_int(file);
711         }
712 #else
713         ship_count = cfread_int(file);
714         weapon_count = cfread_int(file);
715 #endif
716
717         SDL_assert(ship_count <= MAX_SHIP_TYPES);
718         SDL_assert(weapon_count <= MAX_WEAPON_TYPES);
719
720         // read in ship pool
721         for ( i = 0; i < ship_count; i++ ) {
722                 Player_loadout.ship_pool[i] = cfread_int(file);
723         }
724
725         // read in weapons pool
726         for ( i = 0; i < weapon_count; i++ ) {
727                 Player_loadout.weapon_pool[i] = cfread_int(file);
728         }
729
730         // read in loadout info
731         for ( i = 0; i < MAX_WSS_SLOTS; i++ ) {
732                 slot = &Player_loadout.unit_data[i];
733                 slot->ship_class = cfread_int(file);
734                 for ( j = 0; j < MAX_WL_WEAPONS; j++ ) {
735                         slot->wep[j] = cfread_int(file);
736                         slot->wep_count[j] = cfread_int(file);
737                 }
738         }
739 }
740
741 // read_pilot_file()
742 //
743 // returns 0 - file read in correctly
744 //        -1 - .PLR file doesn't exist or file not compatible
745 //        >0 - errno from fopen error
746 // if single == 1, look for players in the single players directory, otherwise look in the 
747 // multiplayers directory
748 int read_pilot_file(const char *callsign, int single, player *p)
749 {
750         ubyte num_ctrls;
751         ubyte is_multi = 0;
752         char filename[MAX_FILENAME_LEN], ship_name[NAME_LENGTH];
753         CFILE   *file;
754         uint id;
755         int i, key_value;
756
757         if (!p) {
758                 SDL_assert((Player_num >= 0) && (Player_num < MAX_PLAYERS));
759                 p = &Players[Player_num];
760         }
761
762         //sprintf(filename, "%-.8s.plr",Players[Player_num].callsign);
763         SDL_assert(strlen(callsign) < MAX_FILENAME_LEN - 4);  // ensure we won't overrun the buffer
764         SDL_strlcpy( filename, callsign, SDL_arraysize(filename) );
765         SDL_strlcat( filename, NOX(".plr"), SDL_arraysize(filename) );
766
767         // if we're a standalone server in multiplayer, just fill in some bogus values since we don't have a pilot file
768         if ((Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_STANDALONE_SERVER)) {
769                 memset(Player, 0, sizeof(player));
770                 SDL_strlcpy(Player->callsign, NOX("Standalone"), SDL_arraysize(Player->callsign));
771                 SDL_strlcpy(Player->short_callsign, NOX("Standalone"), SDL_arraysize(Player->short_callsign));
772                 return 0;
773         }
774         
775         // see comments at the beginning of function
776         if (single) {
777                 file = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
778         } else {
779                 file = cfopen(filename, "rb", CFILE_NORMAL, CF_TYPE_MULTI_PLAYERS);
780         }
781
782         if (!file) {
783                 return errno;
784         }
785
786         id = cfread_uint(file);
787         if (id != PLR_FILE_ID) {
788                 Warning(LOCATION, "Player file has invalid signature");
789                 cfclose(file);
790                 return -1;
791         }
792
793         // check for compatibility here
794         Player_file_version = cfread_uint(file);
795         cf_set_version( file, Player_file_version );
796
797         if (Player_file_version < LOWEST_COMPATIBLE_PLAYER_FILE_VERSION) {
798                 nprintf(("Warning", "WARNING => Player file is outdated and not compatible...\n"));
799                 cfclose(file);
800                 delete_pilot_file( filename, single );
801                 return -1;
802         }
803
804         // read in whether he's a multiplayer or not
805         is_multi = cfread_ubyte(file);
806         if (is_multi){
807                 p->flags |= PLAYER_FLAGS_IS_MULTI;
808         } else {
809                 p->flags &= ~PLAYER_FLAGS_IS_MULTI;     // this takes care of unsetting any leftover bits from a (possibly) previously selected pilot
810         }
811
812 #ifndef FS1_DEMO
813         // read in rank.
814         cfread_int(file);  
815 #endif
816
817         // get player location
818         p->on_bastion = cfread_ubyte(file);
819
820 #ifndef MAKE_FS1
821         // tips?
822         p->tips = cfread_int(file);
823 #endif
824
825         // write out the image file name
826         cfread_string_len(p->image_filename, MAX_FILENAME_LEN - 1, file);
827
828         // write out the image file name
829         p->insignia_texture = -1;
830 #ifndef MAKE_FS1
831         cfread_string_len(p->squad_name, NAME_LENGTH, file);
832         cfread_string_len(p->squad_filename, MAX_FILENAME_LEN - 1, file);
833         player_set_squad_bitmap(p, p->squad_filename);
834 #endif
835
836         // deal with campaign stuff.  The way we store the information in the file is to first store the
837         // name of the current campaign that the player is playing.  Next we store the info regarding the campaigns
838         // that the player is currently playing
839         memset(p->current_campaign, 0, MAX_FILENAME_LEN);
840         cfread_string_len(p->current_campaign, MAX_FILENAME_LEN, file);
841
842         // read in the ship name for last ship flown by the player
843         memset(ship_name, 0, NAME_LENGTH);
844         cfread_string_len(ship_name, NAME_LENGTH, file);
845         p->last_ship_flown_si_index = ship_info_lookup(ship_name);
846         if ( p->last_ship_flown_si_index < 0 ) {
847                 nprintf(("Warning","WARNING => Ship class %s not located in Ship_info[] in player file\n",ship_name));
848                 p->last_ship_flown_si_index = ship_info_lookup(default_player_ship);
849         }
850
851         // set all the entries in the control config arrays to -1 (undefined)
852         control_config_clear();
853
854         // ---------------------------------------------
855         // read in the keyboard/joystick mapping
856         // ---------------------------------------------
857         num_ctrls = cfread_ubyte(file);
858         for (i=0; i<num_ctrls; i++) {
859                 key_value = cfread_short(file);
860                 // NOTE: next two lines are only here for transitioning from 255 to -1 as undefined key items
861                 if (key_value == 255)
862                         key_value = -1;
863
864                 Control_config[i].key_id = keycode_translate_from((short)key_value);
865
866                 key_value = cfread_short(file);
867                 // NOTE: next two lines are only here for transitioning from 255 to -1 as undefined key items
868                 if (key_value == 255)
869                         key_value = -1;
870
871                 Control_config[i].joy_id = (short) key_value;
872         }
873         
874         HUD_config.show_flags = cfread_int(file);       
875         HUD_config.show_flags2 = cfread_int(file);
876
877         HUD_config.popup_flags = cfread_int(file);
878         HUD_config.popup_flags2 = cfread_int(file);
879
880         HUD_config.num_msg_window_lines = cfread_ubyte(file);                   
881         HUD_config.rp_flags = cfread_int(file);
882         HUD_config.rp_dist =    cfread_int(file);
883
884 #ifdef MAKE_FS1
885         HUD_config.main_color = cfread_int(file);
886         HUD_color_alpha = cfread_int(file);
887
888         if ( HUD_color_alpha < HUD_COLOR_ALPHA_USER_MIN ) {
889                 HUD_color_alpha = HUD_COLOR_ALPHA_DEFAULT;
890         }
891
892         hud_config_set_color(HUD_config.main_color);
893 #elif defined(FS2_DEMO)
894         for(i=0; i<NUM_HUD_GAUGES; i++){
895                 cfread(&HUD_config.clr[i], sizeof(color), 1, file);
896         }
897 #else
898         // added 2 gauges with version 137
899         if(Player_file_version < 137){
900                 for(i=0; i<NUM_HUD_GAUGES-2; i++){
901                         cfread(&HUD_config.clr[i], sizeof(color), 1, file);
902                 }
903
904                 // set the 2 new gauges to be radar color
905                 memcpy(&HUD_config.clr[NUM_HUD_GAUGES-2], &HUD_config.clr[HUD_RADAR], sizeof(color));
906                 memcpy(&HUD_config.clr[NUM_HUD_GAUGES-1], &HUD_config.clr[HUD_RADAR], sizeof(color));
907         } else {
908                 for(i=0; i<NUM_HUD_GAUGES; i++){
909                         cfread(&HUD_config.clr[i], sizeof(color), 1, file);
910                 }
911         }
912 #endif
913
914 #ifndef FS1_DEMO
915         // read in the cutscenes which have been viewed
916         Cutscenes_viewable = cfread_int(file);
917 #endif
918
919         Master_sound_volume = cfread_float(file);
920         Master_event_music_volume = cfread_float(file);
921         Master_voice_volume = cfread_float(file);
922
923         audiostream_set_volume_all(Master_voice_volume, ASF_VOICE);
924         audiostream_set_volume_all(Master_event_music_volume, ASF_EVENTMUSIC);
925
926         if ( Master_event_music_volume > 0.0f ) {
927                 Event_music_enabled = 1;
928         } else {
929                 Event_music_enabled = 0;
930         }
931
932         read_detail_settings(file, Player_file_version);
933
934         // restore list of most recently played missions
935         Num_recent_missions = cfread_int( file );
936         SDL_assert(Num_recent_missions <= MAX_RECENT_MISSIONS);
937         for ( i = 0; i < Num_recent_missions; i++ ) {
938                 char *c;
939
940                 cfread_string_len( Recent_missions[i], MAX_FILENAME_LEN, file);
941                 // Remove the extension
942                 c = SDL_strchr(Recent_missions[i], '.');
943                 if (c)
944                         *c = 0;
945         }
946         
947         // use this block of stats from now on
948         read_stats_block(file, Player_file_version, &p->stats); 
949
950    Game_skill_level = cfread_int(file);
951
952         // original axes (4) not bindable, just inverted or not
953         if (Player_file_version < 94) {
954                 // heading, pitch, bank, throttle
955                 for (i = 0; i < 4; i++) {
956                         Invert_axis[i] = cfread_int(file);
957                 }
958
959                 // throttle and roll axes can be disabled (not supported here)
960                 cfread_int(file);
961                 cfread_int(file);
962         } else {
963                 for (i=0; i<NUM_JOY_AXIS_ACTIONS; i++) {
964                         Axis_map_to[i] = cfread_int(file);
965                         Invert_axis[i] = cfread_int(file);
966                 }
967         }
968
969         // restore some player flags
970         Player[Player_num].save_flags = cfread_int(file);
971
972         // restore the most recent ship selection       
973         pilot_read_loadout(file);       
974
975         // read in multiplayer options
976         read_multiplayer_options(p,file);
977
978         p->readyroom_listing_mode = cfread_int(file);
979         Briefing_voice_enabled = cfread_int(file);
980
981         // restore the default netgame protocol mode
982         int protocol_temp = cfread_int(file);
983         switch(protocol_temp){
984                 // TCP & PXO
985                 case NET_VMT:
986                         Multi_options_g.pxo = 1;
987                         Multi_options_g.protocol = NET_TCP;
988                         break;
989
990                 // plain TCP
991                 case NET_TCP:
992                         Multi_options_g.pxo = 0;
993                         Multi_options_g.protocol = NET_TCP;
994                         break;
995
996                 // in case of IPX, which is deprecated
997                 default:
998                         Multi_options_g.pxo = 0;
999                         Multi_options_g.protocol = NET_TCP;
1000                         break;
1001         }
1002
1003         // restore wingman status used by red alert missions
1004         red_alert_read_wingman_status(file, Player_file_version);
1005
1006 #ifdef FS1_DEMO
1007         // HACK - FIXME!!!
1008         // FS1 Demo has a chunk of data, 57 bytes zero'd, which doesn't appear to
1009         // change. No idea what it's for, so this is just for compatabilty sake.
1010         char blank[57];
1011         cfread(blank, SDL_arraysize(blank), 1, file);
1012 #endif
1013
1014         // read techroom data
1015         pilot_read_techroom_data(file);
1016
1017         // restore auto-advance pref
1018         Player->auto_advance = cfread_int(file);
1019
1020         if (Player_file_version >= 94) {
1021                 Use_mouse_to_fly = cfread_int(file);
1022                 Mouse_sensitivity = cfread_int(file);
1023                 Joy_sensitivity = cfread_int(file);
1024                 Dead_zone_size = cfread_int(file);
1025         }
1026
1027         if (cfclose(file))
1028                 return errno;
1029
1030         // restore the callsign into the Player structure
1031         SDL_strlcpy(p->callsign, callsign, SDL_arraysize(p->callsign));
1032
1033         // restore the truncated callsign into Player structure
1034         pilot_set_short_callsign(p, SHORT_CALLSIGN_PIXEL_W);
1035         
1036         // when we store the LastPlayer key, we have to mark it as being single or multiplayer, so we know where to look for him
1037         // (since we could have a single and a multiplayer pilot with the same callsign)
1038         // we'll distinguish them by putting an M and the end of the multiplayer callsign and a P at the end of a single player
1039         char cat[35];
1040
1041         SDL_strlcpy(cat, p->callsign, SDL_arraysize(cat));
1042         if (is_multi)
1043                 SDL_strlcat(cat, NOX("M"), SDL_arraysize(cat));
1044         else
1045                 SDL_strlcat(cat, NOX("S"), SDL_arraysize(cat));
1046
1047         os_config_write_string( NULL, "LastPlayer", cat );
1048 /*
1049         // if he's not a multiplayer pilot, then load in the campaign file at this point!
1050         if (!is_multi) {
1051                 if (mission_campaign_load_by_name(campaign_fname)) {
1052                         strcpy(campaign_fname, BUILTIN_CAMPAIGN);
1053                         if (mission_campaign_load_by_name(campaign_fname))
1054                                 SDL_assert(0);
1055                 }
1056         }
1057         //Campaign.current_mission = mission_num;*/
1058
1059         hud_squadmsg_save_keys();                       // when new pilot read in, must save info for squadmate messaging
1060
1061         return 0;
1062 }
1063
1064 void read_stats_block(CFILE *file, int file_version, scoring_struct *stats)
1065 {
1066         int i, total;
1067    
1068         init_scoring_element(stats);
1069         stats->score = cfread_int(file);
1070         stats->rank = cfread_int(file);
1071         stats->assists = cfread_int(file);
1072
1073 #ifndef MAKE_FS1
1074         if (file_version < 139) {
1075                 // support for FS2_DEMO pilots that still have FS1 medal info in the .plr files
1076                 for (i=0; i < NUM_MEDALS_FS1; i++) {
1077                         cfread_int(file);                       // dummy read
1078                 }
1079         } else {
1080                 // read the usual way
1081                 for (i=0; i < NUM_MEDALS; i++) {
1082                         stats->medals[i] = cfread_int(file);
1083                 }
1084         }
1085 #else
1086         for (i = 0; i < NUM_MEDALS; i++) {
1087                 stats->medals[i] = cfread_int(file);
1088         }
1089 #endif
1090
1091         total = cfread_int(file);
1092         if (total > MAX_SHIP_TYPES){
1093                 Warning(LOCATION, "Some ship kill information will be lost due to MAX_SHIP_TYPES decrease");
1094         }
1095
1096         for (i=0; i<total && i<MAX_SHIP_TYPES; i++){
1097 #ifndef MAKE_FS1
1098                 stats->kills[i] = cfread_ushort(file);
1099 #else
1100                 stats->kills[i] = cfread_int(file);
1101 #endif
1102         }
1103
1104         stats->kill_count = cfread_int(file);
1105         stats->kill_count_ok = cfread_int(file);
1106         
1107         stats->p_shots_fired = cfread_uint(file);
1108         stats->s_shots_fired = cfread_uint(file);
1109         stats->p_shots_hit = cfread_uint(file);
1110         stats->s_shots_hit = cfread_uint(file);
1111         
1112    stats->p_bonehead_hits = cfread_uint(file);
1113         stats->s_bonehead_hits = cfread_uint(file);
1114         stats->bonehead_kills = cfread_uint(file);
1115 }
1116
1117 // grab the various detail settings
1118 void read_detail_settings(CFILE *file, int pfile_version)
1119 {
1120         detail_level_set(NUM_DEFAULT_DETAIL_LEVELS-1);
1121
1122
1123         Detail.setting = cfread_int(file);
1124
1125         Detail.nebula_detail = cfread_int(file);
1126         Detail.detail_distance = cfread_int(file);
1127 #ifdef MAKE_FS1
1128         Detail.weapon_detail = cfread_int(file);
1129 #endif
1130         Detail.hardware_textures = cfread_int(file);
1131         Detail.num_small_debris = cfread_int(file);
1132         Detail.num_particles = cfread_int(file);
1133         Detail.num_stars = cfread_int(file);
1134         Detail.shield_effects = cfread_int(file);
1135         Detail.lighting = cfread_int(file);
1136 #ifdef MAKE_FS1
1137         Detail.unknown_slider = cfread_int(file);
1138 #endif
1139
1140         // Booleans
1141         Detail.targetview_model = cfread_int(file);
1142         Detail.planets_suns = cfread_int(file);
1143 #ifdef MAKE_FS1
1144         Detail.unknown_boolean1 = cfread_int(file);
1145         Detail.unknown_boolean2 = cfread_int(file);
1146         Detail.engine_glows = cfread_int(file);
1147         Detail.alpha_effects = cfread_int(file);
1148 #else
1149         Detail.weapon_extras = cfread_int(file);
1150 #endif
1151 }
1152
1153 // Will write the pilot file in the most current format
1154 //
1155 // if single == 1, save into the single players directory, else save into the multiplayers directory
1156 int write_pilot_file_core(player *p)
1157 {
1158         char filename[MAX_FILENAME_LEN + 1];
1159    int i, si_index;
1160         ubyte is_multi;
1161         CFILE *file;
1162
1163         // never save a pilot file for the standalone server in multiplayer
1164         if ((Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_STANDALONE_SERVER)) {
1165                 return 0;
1166         }
1167
1168         if (!p) {
1169                 SDL_assert((Player_num >= 0) && (Player_num < MAX_PLAYERS));
1170                 p = &Players[Player_num];
1171         }
1172
1173         i = strlen(p->callsign);
1174         if (i == 0)
1175                 return 0;       //      This means there is no player, probably meaning he was deleted and game exited from same screen.
1176
1177         SDL_assert((i > 0) && (i <= MAX_FILENAME_LEN - 4));  // ensure we won't overrun the buffer
1178         SDL_strlcpy( filename, p->callsign, SDL_arraysize(filename));
1179         SDL_strlcat( filename, NOX(".plr"), SDL_arraysize(filename) );
1180
1181         // determine if this pilot is a multiplayer pilot or not
1182         if (p->flags & PLAYER_FLAGS_IS_MULTI){
1183                 is_multi = 1;
1184         } else {
1185                 is_multi = 0;
1186         }
1187
1188         // see above
1189         if ( !is_multi ){
1190                 file = cfopen(filename, "wb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
1191         } else {
1192                 file = cfopen(filename, "wb", CFILE_NORMAL, CF_TYPE_MULTI_PLAYERS);
1193         }
1194
1195         if (!file){
1196                 return errno;
1197         }
1198
1199         // Write out player's info
1200         cfwrite_uint(PLR_FILE_ID, file);
1201         cfwrite_uint(CURRENT_PLAYER_FILE_VERSION, file);
1202
1203         cfwrite_ubyte(is_multi, file);
1204 #ifndef FS1_DEMO
1205         cfwrite_int(p->stats.rank, file);
1206 #endif
1207         cfwrite_ubyte((ubyte) p->on_bastion, file);
1208
1209 #ifndef MAKE_FS1
1210         cfwrite_int(p->tips, file);
1211 #endif
1212
1213         // write out the image file name
1214         cfwrite_string_len(p->image_filename, file);
1215
1216 #ifndef MAKE_FS1
1217         // write out the image file name
1218         cfwrite_string_len(p->squad_name, file);
1219         cfwrite_string_len(p->squad_filename, file);
1220 #endif
1221
1222         // write out the name of the player's active campaign.
1223         cfwrite_string_len(p->current_campaign, file);  
1224
1225         // write the ship name for last ship flown by the player
1226         si_index = p->last_ship_flown_si_index;
1227         if((si_index < 0) || (si_index >= Num_ship_types)){
1228                 si_index = 0;
1229         }
1230
1231         cfwrite_string_len(Ship_info[si_index].name, file);
1232
1233         // ---------------------------------------------
1234         // write the keyboard/joystick configuration
1235         // ---------------------------------------------
1236         short key_id = 0;
1237         cfwrite_ubyte( CCFG_MAX, file );
1238         for (i=0; i<CCFG_MAX; i++) {
1239                 key_id = keycode_translate_to(Control_config[i].key_id);
1240                 cfwrite_short( key_id, file );
1241                 cfwrite_short( Control_config[i].joy_id, file );
1242         }               
1243
1244         // if this hud is an observer, the player ended the last mission as an observer, so we should
1245         // restore his "real" ship HUD.
1246         HUD_CONFIG_TYPE hc_temp;
1247         hc_temp.show_flags = 0;
1248         int stored_observer = 0;
1249         if ( HUD_config.is_observer ){
1250                 // if we're in mission, copy the HUD we're currently using
1251                 if(Game_mode & GM_IN_MISSION){                  
1252                         memcpy(&hc_temp,&HUD_config,sizeof(HUD_CONFIG_TYPE));
1253                         stored_observer = 1;
1254                 }
1255
1256                 hud_config_restore();                           
1257         }
1258
1259         // write the hud configuration
1260         cfwrite_int(HUD_config.show_flags, file);       
1261         cfwrite_int(HUD_config.show_flags2, file);      
1262         cfwrite_int(HUD_config.popup_flags, file);      
1263         cfwrite_int(HUD_config.popup_flags2, file);     
1264         cfwrite_ubyte( (ubyte) HUD_config.num_msg_window_lines, file );
1265         cfwrite_int( HUD_config.rp_flags, file );
1266         cfwrite_int( HUD_config.rp_dist, file );
1267
1268 #ifdef MAKE_FS1
1269         cfwrite_int( HUD_config.main_color, file );
1270         cfwrite_int( HUD_color_alpha, file );
1271 #else
1272         for(i=0; i<NUM_HUD_GAUGES; i++){
1273                 cfwrite(&HUD_config.clr[i], sizeof(color), 1, file);
1274         }
1275 #endif
1276
1277         // restore the HUD we backed up
1278         if( (Game_mode & GM_IN_MISSION) && stored_observer ){           
1279                 memcpy(&HUD_config,&hc_temp,sizeof(HUD_CONFIG_TYPE));
1280         }
1281
1282 #ifndef FS1_DEMO
1283         // write the cutscenes which have been viewed
1284         cfwrite_int(Cutscenes_viewable, file);
1285 #endif
1286
1287         // store the digital sound fx volume, and music volume
1288         cfwrite_float(Master_sound_volume, file);
1289         cfwrite_float(Master_event_music_volume, file);
1290         cfwrite_float(Master_voice_volume, file);
1291
1292
1293         //cfwrite( &Detail, sizeof(detail_levels), 1, file );
1294         write_detail_settings(file);
1295
1296         // store list of most recently played missions
1297         cfwrite_int(Num_recent_missions, file);
1298         for (i=0; i<Num_recent_missions; i++) {
1299                 cfwrite_string_len(Recent_missions[i], file);
1300         }
1301
1302         // write the player stats
1303         write_stats_block(file, &p->stats);     
1304    cfwrite_int(Game_skill_level, file);
1305
1306         for (i=0; i<NUM_JOY_AXIS_ACTIONS; i++) {
1307                 cfwrite_int(Axis_map_to[i], file);
1308                 cfwrite_int(Invert_axis[i], file);
1309         }
1310
1311         // store some player flags
1312    cfwrite_int(Player->save_flags, file);
1313
1314         // store ship selection for most recent mission
1315         pilot_write_loadout(file);
1316
1317         // read in multiplayer options  
1318         write_multiplayer_options(p, file);
1319
1320         cfwrite_int(p->readyroom_listing_mode, file);
1321    cfwrite_int(Briefing_voice_enabled, file);
1322
1323         // store the default netgame protocol mode for this pilot
1324         SDL_assert(Multi_options_g.protocol == NET_TCP);
1325         if (Multi_options_g.pxo == 1) {
1326                 cfwrite_int(NET_VMT, file);
1327         } else {
1328                 cfwrite_int(NET_TCP, file);
1329         }
1330
1331         red_alert_write_wingman_status(file);
1332
1333 #ifdef FS1_DEMO
1334         // HACK - FIXME!!!
1335         // FS1 Demo has a chunk of data, 57 bytes zero'd, which doesn't appear to
1336         // change. No idea what it's for, so this is just for compatabilty sake.
1337         char blank[57];
1338         SDL_zero(blank);
1339         cfwrite(blank, SDL_arraysize(blank), 1, file);
1340 #endif
1341
1342         pilot_write_techroom_data(file);
1343
1344         // store auto-advance pref
1345    cfwrite_int(Player->auto_advance, file);
1346
1347         cfwrite_int(Use_mouse_to_fly, file);
1348         cfwrite_int(Mouse_sensitivity, file);
1349         cfwrite_int(Joy_sensitivity, file);
1350         cfwrite_int(Dead_zone_size, file);
1351
1352         if (!cfclose(file))
1353                 return 0;
1354
1355         return errno;
1356 }
1357
1358 static player *callback_player = NULL;
1359 static void error_writing_callback(int choice)
1360 {
1361         popup_done();
1362
1363         if (choice == 0) {
1364                 // retry
1365                 write_pilot_file(callback_player);
1366         } else if (choice == 1) {
1367                 // ignore
1368         } else {
1369                 // quit game
1370                 exit(1);
1371         }
1372 }
1373
1374 void write_pilot_file(player *the_player)
1375 {
1376         // save for later, in case of a retry
1377         callback_player = the_player;
1378
1379         // write_pilot_file_core returns 0 if ok, non-zero for error
1380         int rval = write_pilot_file_core(the_player);
1381
1382         // check with user if write not successful
1383         if (rval) {
1384                 popup_callback(error_writing_callback, PF_TITLE_RED | PF_TITLE_BIG, 3, XSTR( "&Retry", 41), XSTR( "&Ignore", 42), XSTR( "&Quit Game", 43),
1385                         XSTR( "Warning\nFailed to save pilot file.  You may be out of disk space.  If so, you should press Alt-Tab, free up some disk space, then come back and choose retry.\n", 44) );
1386         }
1387 #ifdef __EMSCRIPTEN__
1388         else {
1389                 // sync files to persistent storage
1390                 EM_ASM(
1391                         FS.syncfs(function(err) {
1392                                 assert(!err);
1393                         });
1394                 );
1395         }
1396 #endif
1397 }
1398
1399 void write_stats_block(CFILE *file,scoring_struct *stats)
1400 {
1401         int i;
1402         int total;
1403
1404         cfwrite_int(stats->score, file);
1405         cfwrite_int(stats->rank, file);
1406         cfwrite_int(stats->assists, file);
1407         for (i=0; i<NUM_MEDALS; i++){
1408                 cfwrite_int(stats->medals[i], file);
1409         }
1410
1411         total = MAX_SHIP_TYPES;
1412         while (total && !stats->kills[total - 1]){  // find last used element
1413                 total--;
1414         }
1415
1416         cfwrite_int(total, file);
1417         for (i=0; i<total; i++){
1418 #ifndef MAKE_FS1
1419                 cfwrite_ushort(stats->kills[i], file);
1420 #else
1421                 cfwrite_int(stats->kills[i], file);
1422 #endif
1423         }
1424
1425         cfwrite_int(stats->kill_count,file);
1426         cfwrite_int(stats->kill_count_ok,file);
1427
1428    cfwrite_uint(stats->p_shots_fired,file);
1429         cfwrite_uint(stats->s_shots_fired,file);
1430         cfwrite_uint(stats->p_shots_hit,file);
1431         cfwrite_uint(stats->s_shots_hit,file);
1432         cfwrite_uint(stats->p_bonehead_hits,file);
1433         cfwrite_uint(stats->s_bonehead_hits,file);
1434         cfwrite_uint(stats->bonehead_kills,file);
1435 }
1436
1437 // write the various detail settings
1438 void write_detail_settings(CFILE *file)
1439 {
1440         cfwrite_int(Detail.setting, file);
1441
1442         cfwrite_int(Detail.nebula_detail, file);
1443         cfwrite_int(Detail.detail_distance, file);
1444 #ifdef MAKE_FS1
1445         cfwrite_int(Detail.weapon_detail, file);
1446 #endif
1447         cfwrite_int(Detail.hardware_textures, file);
1448         cfwrite_int(Detail.num_small_debris, file);
1449         cfwrite_int(Detail.num_particles, file);
1450         cfwrite_int(Detail.num_stars, file);
1451         cfwrite_int(Detail.shield_effects, file);
1452         cfwrite_int(Detail.lighting, file);
1453 #ifdef MAKE_FS1
1454         cfwrite_int(Detail.unknown_slider, file);
1455 #endif
1456
1457         cfwrite_int(Detail.targetview_model, file);
1458         cfwrite_int(Detail.planets_suns, file);
1459 #ifdef MAKE_FS1
1460         cfwrite_int(Detail.unknown_boolean1, file);
1461         cfwrite_int(Detail.unknown_boolean2, file);
1462         cfwrite_int(Detail.engine_glows, file);
1463         cfwrite_int(Detail.alpha_effects, file);
1464 #else
1465         cfwrite_int(Detail.weapon_extras, file);
1466 #endif
1467 }
1468
1469 // write multiplayer information
1470 void write_multiplayer_options(player *p,CFILE *file)
1471 {       
1472         // write the netgame options
1473         cfwrite_ubyte(p->m_server_options.squad_set,file);
1474         cfwrite_ubyte(p->m_server_options.endgame_set,file);    
1475         cfwrite_int(p->m_server_options.flags,file);
1476         cfwrite_uint(p->m_server_options.respawn,file);
1477         cfwrite_ubyte(p->m_server_options.max_observers,file);
1478         cfwrite_ubyte(p->m_server_options.skill_level,file);
1479         cfwrite_ubyte(p->m_server_options.voice_qos,file);
1480         cfwrite_int(p->m_server_options.voice_token_wait,file);
1481         cfwrite_int(p->m_server_options.voice_record_time,file);
1482         cfwrite(&p->m_server_options.mission_time_limit,sizeof(fix),1,file);
1483         cfwrite_int(p->m_server_options.kill_limit,file);
1484
1485         // write the local options
1486         cfwrite_int(p->m_local_options.flags,file);
1487         cfwrite_int(p->m_local_options.obj_update_level,file);
1488 }
1489
1490 // read multiplayer options
1491 void read_multiplayer_options(player *p,CFILE *file)
1492 {
1493         // read the netgame options
1494         p->m_server_options.squad_set = cfread_ubyte(file);
1495         p->m_server_options.endgame_set = cfread_ubyte(file);   
1496         p->m_server_options.flags = cfread_int(file);
1497         p->m_server_options.respawn = cfread_uint(file);
1498         p->m_server_options.max_observers = cfread_ubyte(file);
1499         p->m_server_options.skill_level = cfread_ubyte(file);
1500         p->m_server_options.voice_qos = cfread_ubyte(file);
1501         p->m_server_options.voice_token_wait = cfread_int(file);
1502         p->m_server_options.voice_record_time = cfread_int(file);
1503         cfread(&p->m_server_options.mission_time_limit,sizeof(fix),1,file);
1504         p->m_server_options.kill_limit = cfread_int(file);
1505
1506         // read the local options
1507         p->m_local_options.flags = cfread_int(file);
1508         p->m_local_options.obj_update_level = cfread_int(file);
1509 }
1510
1511 // run thorough an open file (at the beginning) and see if this pilot is a multiplayer pilot
1512 int is_pilot_multi(CFILE *fp)
1513 {
1514         uint id,file_version;
1515         ubyte is_multi;
1516         
1517         id = cfread_uint(fp);
1518         if (id != PLR_FILE_ID) {
1519                 Warning(LOCATION, "Player file has invalid signature");
1520                 cfclose(fp);
1521                 return -1;
1522         }
1523
1524         // check for compatibility here
1525         file_version = cfread_uint(fp);
1526         if (file_version < LOWEST_COMPATIBLE_PLAYER_FILE_VERSION) {
1527                 nprintf(("Warning", "WARNING => Player file is outdated and not compatible...\n"));
1528                 cfclose(fp);
1529                 return -1;
1530         }
1531
1532         // read in whether he's a multiplayer or not
1533         is_multi = cfread_ubyte(fp);
1534         if (is_multi)
1535                 return 1;
1536
1537         return 0;
1538 }
1539
1540 int is_pilot_multi(player *p)
1541 {
1542         return (p->flags & PLAYER_FLAGS_IS_MULTI) ? 1 : 0;
1543 }
1544
1545 // this works on barracks and player_select interface screens
1546 void init_new_pilot(player *p, int reset)
1547 {
1548         int cur_speed;
1549
1550         if (reset) {
1551                 hud_set_default_hud_config(p);          // use a default hud config
1552
1553 #ifndef MAKE_FS1
1554                 // in the demo, load up the hardcoded hcf file
1555 #ifdef FS2_DEMO
1556                 hud_config_color_load("hud_1.hcf");
1557 #else
1558                 hud_config_color_load("hud_3.hcf");
1559 #endif
1560 #else
1561                 hud_config_set_color(HUD_COLOR_GREEN);
1562 #endif
1563
1564                 control_config_reset_defaults();                // get a default keyboard config
1565                 player_set_pilot_defaults(p);                   // set up any player struct defaults
1566
1567                 cur_speed = os_config_read_uint(NULL, NOX("ComputerSpeed"), 2 );
1568                 if ( cur_speed < 0 )    {
1569                         cur_speed = 0;
1570                 } else if ( cur_speed >= NUM_DEFAULT_DETAIL_LEVELS )    {
1571                         cur_speed = NUM_DEFAULT_DETAIL_LEVELS-1;
1572                 }       
1573                 // always set to high
1574                 // DKA: 8/4/99 USE speed from registry
1575                 // cur_speed = NUM_DEFAULT_DETAIL_LEVELS-2;
1576
1577 #if NUM_DEFAULT_DETAIL_LEVELS != 4
1578 #error Code in ManagePilot assumes NUM_DEFAULT_DETAIL_LEVELS = 4
1579 #endif
1580
1581                 detail_level_set(cur_speed);
1582
1583                 Game_skill_level = game_get_default_skill_level();
1584
1585                 mprintf(( "Setting detail level to %d because of new pilot\n", cur_speed ));
1586                 Use_mouse_to_fly = 0;
1587                 Mouse_sensitivity = 4;
1588                 Joy_sensitivity = 9;
1589                 Dead_zone_size = 10;
1590         }
1591
1592         // unassigned squadron
1593         SDL_strlcpy(p->squad_name, XSTR("Unassigned", 1255), SDL_arraysize(p->squad_name));
1594         SDL_strlcpy(p->squad_filename, "", SDL_arraysize(p->squad_filename));
1595
1596         // set him to be a single player pilot by default (the actual creation routines will change this if necessary)
1597         p->flags &= ~PLAYER_FLAGS_IS_MULTI;
1598
1599         // effectively sets the length return by strlen() to 0  
1600         Campaign.filename[0] = 0;
1601         p->on_bastion = 0;
1602
1603         // pick a random pilot image for this guy
1604         if (reset){
1605                 pilot_set_random_pic(p);
1606                 p->insignia_texture = -1;
1607                 pilot_set_random_squad_pic(p);
1608         }
1609
1610         init_scoring_element(&p->stats);        
1611         
1612         p->stats.score = 0;
1613         p->stats.rank = RANK_ENSIGN;    
1614
1615 #ifndef MAKE_FS1
1616         p->tips = 1;
1617 #else
1618         p->tips = 0;
1619 #endif
1620
1621         Multi_options_g.protocol = NET_TCP;     
1622 #ifndef FS1_DEMO
1623         Multi_options_g.pxo = 1;
1624 #endif
1625
1626         // initialize default multiplayer options
1627         multi_options_set_netgame_defaults(&p->m_server_options);
1628         multi_options_set_local_defaults(&p->m_local_options);
1629
1630         Player_loadout.filename[0] = 0;
1631
1632         // reset the cutscenes which can be viewed
1633         if ( reset ){
1634                 Cutscenes_viewable = INTRO_CUTSCENE_FLAG;
1635         }
1636 }
1637
1638 void pilot_set_short_callsign(player *p, int max_width)
1639 {
1640         SDL_strlcpy(p->short_callsign, p->callsign, SDL_arraysize(p->short_callsign));
1641         gr_set_font(FONT1);
1642         gr_force_fit_string(p->short_callsign, CALLSIGN_LEN - 1, max_width);
1643         gr_get_string_size( &(p->short_callsign_width), NULL, p->short_callsign );
1644 }
1645
1646 // pick a random image for the passed player
1647 void pilot_set_random_pic(player *p)
1648 {
1649         // if there are no available pilot pics, set the image filename to null
1650         if (Num_pilot_images <= 0) {
1651                 SDL_strlcpy(p->image_filename, "", SDL_arraysize(p->image_filename));
1652         } else {
1653                 // pick a random name from the list
1654                 int random_index = rand() % Num_pilot_images;
1655                 SDL_assert((random_index >= 0) && (random_index < Num_pilot_images));
1656                 SDL_strlcpy(p->image_filename, Pilot_images_arr[random_index], SDL_arraysize(p->image_filename));
1657         }       
1658 }
1659
1660 // pick a random image for the passed player
1661 void pilot_set_random_squad_pic(player *p)
1662 {       
1663         // if there are no available pilot pics, set the image filename to null
1664         if (Num_pilot_squad_images <= 0) {
1665                 player_set_squad_bitmap(p, "");
1666                 // strcpy(p->squad_filename, "");               
1667         } else {
1668                 // pick a random name from the list
1669                 int random_index = rand() % Num_pilot_squad_images;             
1670                 SDL_assert((random_index >= 0) && (random_index < Num_pilot_squad_images));
1671                 player_set_squad_bitmap(p, Pilot_squad_images_arr[random_index]); 
1672                 // strcpy(p->squad_filename, Pilot_squad_images_arr[random_index]);
1673         }       
1674 }
1675
1676 // format a pilot's callsign into a "personal" form - ie, adding a 's or just an ' as appropriate
1677 void pilot_format_callsign_personal(const char *in_callsign, char *out_callsign, const int out_size)
1678 {
1679         // don't do anything if we've got invalid strings
1680         if((in_callsign == NULL) || (out_callsign == NULL)){
1681                 return;
1682         }
1683
1684         // copy the original string
1685         SDL_strlcpy(out_callsign, in_callsign, out_size);
1686
1687         // tack on the appropriate postfix
1688         if(in_callsign[strlen(in_callsign) - 1] == 's'){                
1689                 SDL_strlcat(out_callsign,XSTR( "\'", 45), out_size);
1690         } else {
1691                 SDL_strlcat(out_callsign,XSTR( "\'s", 46), out_size);
1692         }
1693 }
1694
1695 // throw up a popup asking the user to verify the overwrite of an existing pilot name
1696 // 1 == ok to overwrite, 0 == not ok
1697 int pilot_verify_overwrite()
1698 {
1699         return popup_sync(PF_USE_AFFIRMATIVE_ICON | PF_USE_NEGATIVE_ICON | PF_TITLE_RED | PF_TITLE_BIG, 2, XSTR( "&No", 47), XSTR( "&Yes", 48), XSTR( "Warning\nA duplicate pilot exists\nOverwrite?", 49));
1700 }
1701
1702 extern int Skip_packfile_search;  // located in CFileList.cpp
1703
1704
1705 // load up the list of pilot image filenames (do this at game startup as well as barracks startup)
1706 void pilot_load_pic_list()
1707 {
1708         Num_pilot_images = 0;
1709         
1710         // load pilot images from the player images directory
1711         Num_pilot_images = cf_get_file_list_preallocated(MAX_PILOT_IMAGES, Pilot_images_arr, Pilot_image_names, CF_TYPE_PLAYER_IMAGES_MAIN, NOX("*.pcx"));
1712
1713         // sort all filenames
1714         cf_sort_filenames(Num_pilot_images, Pilot_image_names, CF_SORT_NAME);
1715 }
1716
1717 // load up the list of pilot squad filenames
1718 void pilot_load_squad_pic_list()
1719 {
1720         Num_pilot_squad_images = 0;
1721         
1722         // load pilot images from the player images directory
1723         Num_pilot_squad_images = cf_get_file_list_preallocated(MAX_PILOT_IMAGES, Pilot_squad_images_arr, Pilot_squad_image_names, CF_TYPE_SQUAD_IMAGES_MAIN, NOX("*.pcx"));
1724
1725         // sort all filenames
1726         cf_sort_filenames(Num_pilot_squad_images, Pilot_squad_image_names, CF_SORT_NAME);
1727 }
1728
1729 // will attempt to load an insignia bitmap and set it as active for the player
1730 void player_set_squad_bitmap(player *p, const char *fname)
1731 {
1732         // sanity check
1733         if(p == NULL){
1734                 return;
1735         }
1736
1737         // if he has another bitmap already - unload it
1738         if(p->insignia_texture >= 0){
1739                 bm_unload(p->insignia_texture);
1740         }
1741
1742         p->insignia_texture = -1;
1743
1744         // try and set the new one
1745         if (fname != p->squad_filename) {
1746                 SDL_strlcpy(p->squad_filename, fname, SDL_arraysize(p->squad_filename));
1747         }
1748
1749         if(strlen(p->squad_filename) > 0){
1750                 p->insignia_texture = bm_load_duplicate(fname);
1751                 
1752                 // lock is as a transparent texture
1753                 if(p->insignia_texture != -1){
1754                         bm_lock(p->insignia_texture, 16, BMP_TEX_XPARENT);
1755                         bm_unlock(p->insignia_texture);
1756                 }
1757         }
1758
1759         /*
1760         flen = strlen(filename);
1761         elen = strlen(ext);
1762         SDL_assert(flen < MAX_PATH_LEN);
1763         strcpy(path, filename);
1764         if ((flen < 4) || SDL_strcasecmp(path + flen - elen, ext)) {
1765                 SDL_assert(flen + elen < MAX_PATH_LEN);
1766                 strcat(path, ext);
1767         }
1768         */
1769 }
1770
1771 // set squadron
1772 void player_set_squad(player *p, const char *squad_name)
1773 {
1774         // sanity check
1775         if(p == NULL){
1776                 return;
1777         }
1778
1779         SDL_strlcpy(p->squad_name, squad_name, SDL_arraysize(p->squad_name));
1780 }
1781
1782 DCF(pilot,"Changes pilot stats. (Like reset campaign)" )
1783 {
1784         if (Dc_command) {
1785                 dc_get_arg(ARG_STRING);
1786                 if (!strcmp(Dc_arg, NOX("reset"))) {
1787                         if (strlen(Campaign.filename)) {
1788                                 mission_campaign_savefile_delete(Campaign.filename);
1789                                 mission_campaign_load(Campaign.filename);
1790                         }
1791
1792                 } else {
1793                         Dc_help = 1;  // print usage, not stats
1794                 }
1795         }
1796
1797         if (Dc_help) {
1798                 dc_printf( "Usage: pilot keyword\nWhere keyword can be in the following forms:\n" );
1799                 dc_printf( "pilot reset                 Resets campaign stats.\n" );
1800                 Dc_status = 0;  // don't print status if help is printed.  Too messy.
1801         }
1802
1803         if (Dc_status) {
1804                 // no stats
1805         }
1806 }
1807
1808 struct fs_keycode_t {
1809         short fs_code;
1810         SDL_Keycode sdl_code;
1811 };
1812
1813 static const fs_keycode_t keycode_lookup[] = {
1814         { /* KEY_0 */           0x0B,   SDLK_0                  },
1815         { /* KEY_1 */           0x02,   SDLK_1                  },
1816         { /* KEY_2 */           0x03,   SDLK_2                  },
1817         { /* KEY_3 */           0x04,   SDLK_3                  },
1818         { /* KEY_4 */           0x05,   SDLK_4                  },
1819         { /* KEY_5 */           0x06,   SDLK_5                  },
1820         { /* KEY_6 */           0x07,   SDLK_6                  },
1821         { /* KEY_7 */           0x08,   SDLK_7                  },
1822         { /* KEY_8 */           0x09,   SDLK_8                  },
1823         { /* KEY_9 */           0x0A,   SDLK_9                  },
1824         { /* KEY_A */           0x1E,   SDLK_a                  },
1825         { /* KEY_B */           0x30,   SDLK_b                  },
1826         { /* KEY_C */           0x2E,   SDLK_c                  },
1827         { /* KEY_D */           0x20,   SDLK_d                  },
1828         { /* KEY_E */           0x12,   SDLK_e                  },
1829         { /* KEY_F */           0x21,   SDLK_f                  },
1830         { /* KEY_G */           0x22,   SDLK_g                  },
1831         { /* KEY_H */           0x23,   SDLK_h                  },
1832         { /* KEY_I */           0x17,   SDLK_i                  },
1833         { /* KEY_J */           0x24,   SDLK_j                  },
1834         { /* KEY_K */           0x25,   SDLK_k                  },
1835         { /* KEY_L */           0x26,   SDLK_l                  },
1836         { /* KEY_M */           0x32,   SDLK_m                  },
1837         { /* KEY_N */           0x31,   SDLK_n                  },
1838         { /* KEY_O */           0x18,   SDLK_o                  },
1839         { /* KEY_P */           0x19,   SDLK_p                  },
1840         { /* KEY_Q */           0x10,   SDLK_q                  },
1841         { /* KEY_R */           0x13,   SDLK_r                  },
1842         { /* KEY_S */           0x1F,   SDLK_s                  },
1843         { /* KEY_T */           0x14,   SDLK_t                  },
1844         { /* KEY_U */           0x16,   SDLK_u                  },
1845         { /* KEY_V */           0x2F,   SDLK_v                  },
1846         { /* KEY_W */           0x11,   SDLK_w                  },
1847         { /* KEY_X */           0x2D,   SDLK_x                  },
1848         { /* KEY_Y */           0x15,   SDLK_y                  },
1849         { /* KEY_Z */           0x2C,   SDLK_z                  },
1850         { /* KEY_MINUS */       0x0C,   SDLK_MINUS              },
1851         { /* KEY_EQUAL */       0x0D,   SDLK_EQUALS             },
1852         { /* KEY_DIVIDE */      0x35,   SDLK_SLASH              },
1853         { /* KEY_SLASH */       0x2B,   SDLK_BACKSLASH  },
1854         { /* KEY_SLASH_UK */    0x56,   SDLK_BACKSLASH  },
1855         { /* KEY_COMMA */       0x33,   SDLK_COMMA              },
1856         { /* KEY_PERIOD */      0x34,   SDLK_PERIOD             },
1857         { /* KEY_SEMICOL */     0x27,   SDLK_SEMICOLON  },
1858         { /* KEY_LBRACKET */    0x1A,   SDLK_LEFTBRACKET        },
1859         { /* KEY_RBRACKET */    0x1B,   SDLK_RIGHTBRACKET       },
1860         { /* KEY_RAPOSTRO */    0x28,   SDLK_QUOTE              },
1861         { /* KEY_LAPOSTRO */    0x29,   SDLK_BACKQUOTE  },
1862         { /* KEY_ESC */         0x01,   SDLK_ESCAPE             },
1863         { /* KEY_ENTER */       0x1C,   SDLK_RETURN             },
1864         { /* KEY_BACKSP */      0x0E,   SDLK_BACKSPACE  },
1865         { /* KEY_TAB */         0x0F,   SDLK_TAB                },
1866         { /* KEY_SPACEBAR */    0x39,   SDLK_SPACE              },
1867         { /* KEY_NUMLOCK */     0x61,   SDLK_NUMLOCKCLEAR       },
1868         { /* KEY_SCROLLOCK */   0x46,   SDLK_SCROLLLOCK },
1869         { /* KEY_CAPSLOCK */    0x3A,   SDLK_CAPSLOCK   },
1870         { /* KEY_LSHIFT */      0x2A,   SDLK_LSHIFT             },
1871         { /* KEY_RSHIFT */      0x36,   SDLK_RSHIFT             },
1872         { /* KEY_LALT */        0x38,   SDLK_LALT               },
1873         { /* KEY_RALT */        0xB8,   SDLK_RALT               },
1874         { /* KEY_LCTRL */       0x1D,   SDLK_LCTRL              },
1875         { /* KEY_RCTRL */       0x9D,   SDLK_RCTRL              },
1876         { /* KEY_F1 */          0x3B,   SDLK_F1                 },
1877         { /* KEY_F2 */          0x3C,   SDLK_F2                 },
1878         { /* KEY_F3 */          0x3D,   SDLK_F3                 },
1879         { /* KEY_F4 */          0x3E,   SDLK_F4                 },
1880         { /* KEY_F5 */          0x3F,   SDLK_F5                 },
1881         { /* KEY_F6 */          0x40,   SDLK_F6                 },
1882         { /* KEY_F7 */          0x41,   SDLK_F7                 },
1883         { /* KEY_F8 */          0x42,   SDLK_F8                 },
1884         { /* KEY_F9 */          0x43,   SDLK_F9                 },
1885         { /* KEY_F10 */         0x44,   SDLK_F10                },
1886         { /* KEY_F11 */         0x57,   SDLK_F11                },
1887         { /* KEY_F12 */         0x58,   SDLK_F12                },
1888         { /* KEY_PAD0 */        0x52,   SDLK_KP_0               },
1889         { /* KEY_PAD1 */        0x4F,   SDLK_KP_1               },
1890         { /* KEY_PAD2 */        0x50,   SDLK_KP_2               },
1891         { /* KEY_PAD3 */        0x51,   SDLK_KP_3               },
1892         { /* KEY_PAD4 */        0x4B,   SDLK_KP_4               },
1893         { /* KEY_PAD5 */        0x4C,   SDLK_KP_5               },
1894         { /* KEY_PAD6 */        0x4D,   SDLK_KP_6               },
1895         { /* KEY_PAD7 */        0x47,   SDLK_KP_7               },
1896         { /* KEY_PAD8 */        0x48,   SDLK_KP_8               },
1897         { /* KEY_PAD9 */        0x49,   SDLK_KP_9               },
1898         { /* KEY_PADMINUS */    0x4A,   SDLK_KP_MINUS   },
1899         { /* KEY_PADPLUS */     0x4E,   SDLK_KP_PLUS    },
1900         { /* KEY_PADPERIOD */   0x53,   SDLK_KP_PERIOD  },
1901         { /* KEY_PADDIVIDE */   0xB5,   SDLK_KP_DIVIDE  },
1902         { /* KEY_PADMULTIPLY */ 0x37,   SDLK_KP_MULTIPLY        },
1903         { /* KEY_PADENTER */    0x9C,   SDLK_KP_ENTER   },
1904         { /* KEY_INSERT */      0xD2,   SDLK_INSERT             },
1905         { /* KEY_HOME */        0xC7,   SDLK_HOME               },
1906         { /* KEY_PAGEUP */      0xC9,   SDLK_PAGEUP             },
1907         { /* KEY_DELETE */      0xd3,   SDLK_DELETE             },
1908         { /* KEY_END */         0xCF,   SDLK_END                },
1909         { /* KEY_PAGEDOWN */    0xD1,   SDLK_PAGEDOWN   },
1910         { /* KEY_UP */          0xC8,   SDLK_UP                 },
1911         { /* KEY_DOWN */        0xD0,   SDLK_DOWN               },
1912         { /* KEY_LEFT */        0xCB,   SDLK_LEFT               },
1913         { /* KEY_RIGHT */       0xCD,   SDLK_RIGHT              },
1914         { /* KEY_PRINT_SCRN */  0xB7,   SDLK_PRINTSCREEN        },
1915         { /* KEY_PAUSE */       0x45,   SDLK_PAUSE              },
1916         { /* KEY_BREAK */       0xc6,   SDLK_PAUSE              }
1917 };
1918
1919 static SDL_Keycode keycode_translate_from(short keycode)
1920 {
1921         const int tbl_size = sizeof(keycode_lookup) / sizeof(fs_keycode_t);
1922
1923         if (keycode < 0) {
1924                 return -1;
1925         }
1926
1927         int mods = keycode & 0xf900;
1928         keycode &= KEY_MASK;
1929
1930         for (int i = 0; i < tbl_size; i++) {
1931                 if (keycode_lookup[i].fs_code == keycode) {
1932                         return (SDL_Keycode)(keycode_lookup[i].sdl_code | mods);
1933                 }
1934         }
1935
1936         return -1;
1937 }
1938
1939 static short keycode_translate_to(SDL_Keycode keycode)
1940 {
1941         const int tbl_size = sizeof(keycode_lookup) / sizeof(fs_keycode_t);
1942
1943         if (keycode < 0) {
1944                 return -1;
1945         }
1946
1947         int mods = keycode & 0xf900;
1948         keycode &= KEY_MASK;
1949
1950         for (int i = 0; i < tbl_size; i++) {
1951                 if (keycode_lookup[i].sdl_code == keycode) {
1952                         return (short)(keycode_lookup[i].fs_code | mods);
1953                 }
1954         }
1955
1956         return -1;
1957 }