]> icculus.org git repositories - taylor/freespace2.git/blob - src/playerman/managepilot.cpp
make first pass at async popups
[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         char blank[57];
1009         SDL_zero(blank);
1010         cfread(blank, SDL_arraysize(blank), 1, file);
1011 #endif
1012
1013         // read techroom data
1014         pilot_read_techroom_data(file);
1015
1016         // restore auto-advance pref
1017         Player->auto_advance = cfread_int(file);
1018
1019         if (Player_file_version >= 94) {
1020                 Use_mouse_to_fly = cfread_int(file);
1021                 Mouse_sensitivity = cfread_int(file);
1022                 Joy_sensitivity = cfread_int(file);
1023                 Dead_zone_size = cfread_int(file);
1024         }
1025
1026         if (cfclose(file))
1027                 return errno;
1028
1029         // restore the callsign into the Player structure
1030         SDL_strlcpy(p->callsign, callsign, SDL_arraysize(p->callsign));
1031
1032         // restore the truncated callsign into Player structure
1033         pilot_set_short_callsign(p, SHORT_CALLSIGN_PIXEL_W);
1034         
1035         // when we store the LastPlayer key, we have to mark it as being single or multiplayer, so we know where to look for him
1036         // (since we could have a single and a multiplayer pilot with the same callsign)
1037         // 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
1038         char cat[35];
1039
1040         SDL_strlcpy(cat, p->callsign, SDL_arraysize(cat));
1041         if (is_multi)
1042                 SDL_strlcat(cat, NOX("M"), SDL_arraysize(cat));
1043         else
1044                 SDL_strlcat(cat, NOX("S"), SDL_arraysize(cat));
1045
1046         os_config_write_string( NULL, "LastPlayer", cat );
1047 /*
1048         // if he's not a multiplayer pilot, then load in the campaign file at this point!
1049         if (!is_multi) {
1050                 if (mission_campaign_load_by_name(campaign_fname)) {
1051                         strcpy(campaign_fname, BUILTIN_CAMPAIGN);
1052                         if (mission_campaign_load_by_name(campaign_fname))
1053                                 SDL_assert(0);
1054                 }
1055         }
1056         //Campaign.current_mission = mission_num;*/
1057
1058         hud_squadmsg_save_keys();                       // when new pilot read in, must save info for squadmate messaging
1059
1060         return 0;
1061 }
1062
1063 void read_stats_block(CFILE *file, int file_version, scoring_struct *stats)
1064 {
1065         int i, total;
1066    
1067         init_scoring_element(stats);
1068         stats->score = cfread_int(file);
1069         stats->rank = cfread_int(file);
1070         stats->assists = cfread_int(file);
1071
1072 #ifndef MAKE_FS1
1073         if (file_version < 139) {
1074                 // support for FS2_DEMO pilots that still have FS1 medal info in the .plr files
1075                 for (i=0; i < NUM_MEDALS_FS1; i++) {
1076                         cfread_int(file);                       // dummy read
1077                 }
1078         } else {
1079                 // read the usual way
1080                 for (i=0; i < NUM_MEDALS; i++) {
1081                         stats->medals[i] = cfread_int(file);
1082                 }
1083         }
1084 #else
1085         for (i = 0; i < NUM_MEDALS; i++) {
1086                 stats->medals[i] = cfread_int(file);
1087         }
1088 #endif
1089
1090         total = cfread_int(file);
1091         if (total > MAX_SHIP_TYPES){
1092                 Warning(LOCATION, "Some ship kill information will be lost due to MAX_SHIP_TYPES decrease");
1093         }
1094
1095         for (i=0; i<total && i<MAX_SHIP_TYPES; i++){
1096 #ifndef MAKE_FS1
1097                 stats->kills[i] = cfread_ushort(file);
1098 #else
1099                 stats->kills[i] = cfread_int(file);
1100 #endif
1101         }
1102
1103         stats->kill_count = cfread_int(file);
1104         stats->kill_count_ok = cfread_int(file);
1105         
1106         stats->p_shots_fired = cfread_uint(file);
1107         stats->s_shots_fired = cfread_uint(file);
1108         stats->p_shots_hit = cfread_uint(file);
1109         stats->s_shots_hit = cfread_uint(file);
1110         
1111    stats->p_bonehead_hits = cfread_uint(file);
1112         stats->s_bonehead_hits = cfread_uint(file);
1113         stats->bonehead_kills = cfread_uint(file);
1114 }
1115
1116 // grab the various detail settings
1117 void read_detail_settings(CFILE *file, int pfile_version)
1118 {
1119         detail_level_set(NUM_DEFAULT_DETAIL_LEVELS-1);
1120
1121
1122         Detail.setting = cfread_int(file);
1123
1124         Detail.nebula_detail = cfread_int(file);
1125         Detail.detail_distance = cfread_int(file);
1126 #ifdef MAKE_FS1
1127         Detail.weapon_detail = cfread_int(file);
1128 #endif
1129         Detail.hardware_textures = cfread_int(file);
1130         Detail.num_small_debris = cfread_int(file);
1131         Detail.num_particles = cfread_int(file);
1132         Detail.num_stars = cfread_int(file);
1133         Detail.shield_effects = cfread_int(file);
1134         Detail.lighting = cfread_int(file);
1135 #ifdef MAKE_FS1
1136         Detail.unknown_slider = cfread_int(file);
1137 #endif
1138
1139         // Booleans
1140         Detail.targetview_model = cfread_int(file);
1141         Detail.planets_suns = cfread_int(file);
1142 #ifdef MAKE_FS1
1143         Detail.unknown_boolean1 = cfread_int(file);
1144         Detail.unknown_boolean2 = cfread_int(file);
1145         Detail.engine_glows = cfread_int(file);
1146         Detail.alpha_effects = cfread_int(file);
1147 #else
1148         Detail.weapon_extras = cfread_int(file);
1149 #endif
1150 }
1151
1152 // Will write the pilot file in the most current format
1153 //
1154 // if single == 1, save into the single players directory, else save into the multiplayers directory
1155 int write_pilot_file_core(player *p)
1156 {
1157         char filename[MAX_FILENAME_LEN + 1];
1158    int i, si_index;
1159         ubyte is_multi;
1160         CFILE *file;
1161
1162         // never save a pilot file for the standalone server in multiplayer
1163         if ((Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_STANDALONE_SERVER)) {
1164                 return 0;
1165         }
1166
1167         if (!p) {
1168                 SDL_assert((Player_num >= 0) && (Player_num < MAX_PLAYERS));
1169                 p = &Players[Player_num];
1170         }
1171
1172         i = strlen(p->callsign);
1173         if (i == 0)
1174                 return 0;       //      This means there is no player, probably meaning he was deleted and game exited from same screen.
1175
1176         SDL_assert((i > 0) && (i <= MAX_FILENAME_LEN - 4));  // ensure we won't overrun the buffer
1177         SDL_strlcpy( filename, p->callsign, SDL_arraysize(filename));
1178         SDL_strlcat( filename, NOX(".plr"), SDL_arraysize(filename) );
1179
1180         // determine if this pilot is a multiplayer pilot or not
1181         if (p->flags & PLAYER_FLAGS_IS_MULTI){
1182                 is_multi = 1;
1183         } else {
1184                 is_multi = 0;
1185         }
1186
1187         // see above
1188         if ( !is_multi ){
1189                 file = cfopen(filename, "wb", CFILE_NORMAL, CF_TYPE_SINGLE_PLAYERS);
1190         } else {
1191                 file = cfopen(filename, "wb", CFILE_NORMAL, CF_TYPE_MULTI_PLAYERS);
1192         }
1193
1194         if (!file){
1195                 return errno;
1196         }
1197
1198         // Write out player's info
1199         cfwrite_uint(PLR_FILE_ID, file);
1200         cfwrite_uint(CURRENT_PLAYER_FILE_VERSION, file);
1201
1202         cfwrite_ubyte(is_multi, file);
1203 #ifndef FS1_DEMO
1204         cfwrite_int(p->stats.rank, file);
1205 #endif
1206         cfwrite_ubyte((ubyte) p->on_bastion, file);
1207
1208 #ifndef MAKE_FS1
1209         cfwrite_int(p->tips, file);
1210 #endif
1211
1212         // write out the image file name
1213         cfwrite_string_len(p->image_filename, file);
1214
1215 #ifndef MAKE_FS1
1216         // write out the image file name
1217         cfwrite_string_len(p->squad_name, file);
1218         cfwrite_string_len(p->squad_filename, file);
1219 #endif
1220
1221         // write out the name of the player's active campaign.
1222         cfwrite_string_len(p->current_campaign, file);  
1223
1224         // write the ship name for last ship flown by the player
1225         si_index = p->last_ship_flown_si_index;
1226         if((si_index < 0) || (si_index >= Num_ship_types)){
1227                 si_index = 0;
1228         }
1229
1230         cfwrite_string_len(Ship_info[si_index].name, file);
1231
1232         // ---------------------------------------------
1233         // write the keyboard/joystick configuration
1234         // ---------------------------------------------
1235         short key_id = 0;
1236         cfwrite_ubyte( CCFG_MAX, file );
1237         for (i=0; i<CCFG_MAX; i++) {
1238                 key_id = keycode_translate_to(Control_config[i].key_id);
1239                 cfwrite_short( key_id, file );
1240                 cfwrite_short( Control_config[i].joy_id, file );
1241         }               
1242
1243         // if this hud is an observer, the player ended the last mission as an observer, so we should
1244         // restore his "real" ship HUD.
1245         HUD_CONFIG_TYPE hc_temp;
1246         hc_temp.show_flags = 0;
1247         int stored_observer = 0;
1248         if ( HUD_config.is_observer ){
1249                 // if we're in mission, copy the HUD we're currently using
1250                 if(Game_mode & GM_IN_MISSION){                  
1251                         memcpy(&hc_temp,&HUD_config,sizeof(HUD_CONFIG_TYPE));
1252                         stored_observer = 1;
1253                 }
1254
1255                 hud_config_restore();                           
1256         }
1257
1258         // write the hud configuration
1259         cfwrite_int(HUD_config.show_flags, file);       
1260         cfwrite_int(HUD_config.show_flags2, file);      
1261         cfwrite_int(HUD_config.popup_flags, file);      
1262         cfwrite_int(HUD_config.popup_flags2, file);     
1263         cfwrite_ubyte( (ubyte) HUD_config.num_msg_window_lines, file );
1264         cfwrite_int( HUD_config.rp_flags, file );
1265         cfwrite_int( HUD_config.rp_dist, file );
1266
1267 #ifdef MAKE_FS1
1268         cfwrite_int( HUD_config.main_color, file );
1269         cfwrite_int( HUD_color_alpha, file );
1270 #else
1271         for(i=0; i<NUM_HUD_GAUGES; i++){
1272                 cfwrite(&HUD_config.clr[i], sizeof(color), 1, file);
1273         }
1274 #endif
1275
1276         // restore the HUD we backed up
1277         if( (Game_mode & GM_IN_MISSION) && stored_observer ){           
1278                 memcpy(&HUD_config,&hc_temp,sizeof(HUD_CONFIG_TYPE));
1279         }
1280
1281 #ifndef FS1_DEMO
1282         // write the cutscenes which have been viewed
1283         cfwrite_int(Cutscenes_viewable, file);
1284 #endif
1285
1286         // store the digital sound fx volume, and music volume
1287         cfwrite_float(Master_sound_volume, file);
1288         cfwrite_float(Master_event_music_volume, file);
1289         cfwrite_float(Master_voice_volume, file);
1290
1291
1292         //cfwrite( &Detail, sizeof(detail_levels), 1, file );
1293         write_detail_settings(file);
1294
1295         // store list of most recently played missions
1296         cfwrite_int(Num_recent_missions, file);
1297         for (i=0; i<Num_recent_missions; i++) {
1298                 cfwrite_string_len(Recent_missions[i], file);
1299         }
1300
1301         // write the player stats
1302         write_stats_block(file, &p->stats);     
1303    cfwrite_int(Game_skill_level, file);
1304
1305         for (i=0; i<NUM_JOY_AXIS_ACTIONS; i++) {
1306                 cfwrite_int(Axis_map_to[i], file);
1307                 cfwrite_int(Invert_axis[i], file);
1308         }
1309
1310         // store some player flags
1311    cfwrite_int(Player->save_flags, file);
1312
1313         // store ship selection for most recent mission
1314         pilot_write_loadout(file);
1315
1316         // read in multiplayer options  
1317         write_multiplayer_options(p, file);
1318
1319         cfwrite_int(p->readyroom_listing_mode, file);
1320    cfwrite_int(Briefing_voice_enabled, file);
1321
1322         // store the default netgame protocol mode for this pilot
1323         SDL_assert(Multi_options_g.protocol == NET_TCP);
1324         if (Multi_options_g.pxo == 1) {
1325                 cfwrite_int(NET_VMT, file);
1326         } else {
1327                 cfwrite_int(NET_TCP, file);
1328         }
1329
1330         red_alert_write_wingman_status(file);
1331 #ifdef FS1_DEMO
1332         // HACK - FIXME!!!
1333         char blank[57];
1334         SDL_zero(blank);
1335         cfwrite(blank, SDL_arraysize(blank), 1, file);
1336 #endif
1337         pilot_write_techroom_data(file);
1338
1339         // store auto-advance pref
1340    cfwrite_int(Player->auto_advance, file);
1341
1342         cfwrite_int(Use_mouse_to_fly, file);
1343         cfwrite_int(Mouse_sensitivity, file);
1344         cfwrite_int(Joy_sensitivity, file);
1345         cfwrite_int(Dead_zone_size, file);
1346
1347         if (!cfclose(file))
1348                 return 0;
1349
1350         return errno;
1351 }
1352
1353 static player *callback_player = NULL;
1354 static void error_writing_callback(int choice)
1355 {
1356         popup_done();
1357
1358         if (choice == 0) {
1359                 // retry
1360                 write_pilot_file(callback_player);
1361         } else if (choice == 1) {
1362                 // ignore
1363         } else {
1364                 // quit game
1365                 exit(1);
1366         }
1367 }
1368
1369 void write_pilot_file(player *the_player)
1370 {
1371         // save for later, in case of a retry
1372         callback_player = the_player;
1373
1374         // write_pilot_file_core returns 0 if ok, non-zero for error
1375         int rval = write_pilot_file_core(the_player);
1376
1377         // check with user if write not successful
1378         if (rval) {
1379                 popup_callback(error_writing_callback, PF_TITLE_RED | PF_TITLE_BIG, 3, XSTR( "&Retry", 41), XSTR( "&Ignore", 42), XSTR( "&Quit Game", 43),
1380                         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) );
1381         }
1382 }
1383
1384 void write_stats_block(CFILE *file,scoring_struct *stats)
1385 {
1386         int i;
1387         int total;
1388
1389         cfwrite_int(stats->score, file);
1390         cfwrite_int(stats->rank, file);
1391         cfwrite_int(stats->assists, file);
1392         for (i=0; i<NUM_MEDALS; i++){
1393                 cfwrite_int(stats->medals[i], file);
1394         }
1395
1396         total = MAX_SHIP_TYPES;
1397         while (total && !stats->kills[total - 1]){  // find last used element
1398                 total--;
1399         }
1400
1401         cfwrite_int(total, file);
1402         for (i=0; i<total; i++){
1403 #ifndef MAKE_FS1
1404                 cfwrite_ushort(stats->kills[i], file);
1405 #else
1406                 cfwrite_int(stats->kills[i], file);
1407 #endif
1408         }
1409
1410         cfwrite_int(stats->kill_count,file);
1411         cfwrite_int(stats->kill_count_ok,file);
1412
1413    cfwrite_uint(stats->p_shots_fired,file);
1414         cfwrite_uint(stats->s_shots_fired,file);
1415         cfwrite_uint(stats->p_shots_hit,file);
1416         cfwrite_uint(stats->s_shots_hit,file);
1417         cfwrite_uint(stats->p_bonehead_hits,file);
1418         cfwrite_uint(stats->s_bonehead_hits,file);
1419         cfwrite_uint(stats->bonehead_kills,file);
1420 }
1421
1422 // write the various detail settings
1423 void write_detail_settings(CFILE *file)
1424 {
1425         cfwrite_int(Detail.setting, file);
1426
1427         cfwrite_int(Detail.nebula_detail, file);
1428         cfwrite_int(Detail.detail_distance, file);
1429 #ifdef MAKE_FS1
1430         cfwrite_int(Detail.weapon_detail, file);
1431 #endif
1432         cfwrite_int(Detail.hardware_textures, file);
1433         cfwrite_int(Detail.num_small_debris, file);
1434         cfwrite_int(Detail.num_particles, file);
1435         cfwrite_int(Detail.num_stars, file);
1436         cfwrite_int(Detail.shield_effects, file);
1437         cfwrite_int(Detail.lighting, file);
1438 #ifdef MAKE_FS1
1439         cfwrite_int(Detail.unknown_slider, file);
1440 #endif
1441
1442         cfwrite_int(Detail.targetview_model, file);
1443         cfwrite_int(Detail.planets_suns, file);
1444 #ifdef MAKE_FS1
1445         cfwrite_int(Detail.unknown_boolean1, file);
1446         cfwrite_int(Detail.unknown_boolean2, file);
1447         cfwrite_int(Detail.engine_glows, file);
1448         cfwrite_int(Detail.alpha_effects, file);
1449 #else
1450         cfwrite_int(Detail.weapon_extras, file);
1451 #endif
1452 }
1453
1454 // write multiplayer information
1455 void write_multiplayer_options(player *p,CFILE *file)
1456 {       
1457         // write the netgame options
1458         cfwrite_ubyte(p->m_server_options.squad_set,file);
1459         cfwrite_ubyte(p->m_server_options.endgame_set,file);    
1460         cfwrite_int(p->m_server_options.flags,file);
1461         cfwrite_uint(p->m_server_options.respawn,file);
1462         cfwrite_ubyte(p->m_server_options.max_observers,file);
1463         cfwrite_ubyte(p->m_server_options.skill_level,file);
1464         cfwrite_ubyte(p->m_server_options.voice_qos,file);
1465         cfwrite_int(p->m_server_options.voice_token_wait,file);
1466         cfwrite_int(p->m_server_options.voice_record_time,file);
1467         cfwrite(&p->m_server_options.mission_time_limit,sizeof(fix),1,file);
1468         cfwrite_int(p->m_server_options.kill_limit,file);
1469
1470         // write the local options
1471         cfwrite_int(p->m_local_options.flags,file);
1472         cfwrite_int(p->m_local_options.obj_update_level,file);
1473 }
1474
1475 // read multiplayer options
1476 void read_multiplayer_options(player *p,CFILE *file)
1477 {
1478         // read the netgame options
1479         p->m_server_options.squad_set = cfread_ubyte(file);
1480         p->m_server_options.endgame_set = cfread_ubyte(file);   
1481         p->m_server_options.flags = cfread_int(file);
1482         p->m_server_options.respawn = cfread_uint(file);
1483         p->m_server_options.max_observers = cfread_ubyte(file);
1484         p->m_server_options.skill_level = cfread_ubyte(file);
1485         p->m_server_options.voice_qos = cfread_ubyte(file);
1486         p->m_server_options.voice_token_wait = cfread_int(file);
1487         p->m_server_options.voice_record_time = cfread_int(file);
1488         cfread(&p->m_server_options.mission_time_limit,sizeof(fix),1,file);
1489         p->m_server_options.kill_limit = cfread_int(file);
1490
1491         // read the local options
1492         p->m_local_options.flags = cfread_int(file);
1493         p->m_local_options.obj_update_level = cfread_int(file);
1494 }
1495
1496 // run thorough an open file (at the beginning) and see if this pilot is a multiplayer pilot
1497 int is_pilot_multi(CFILE *fp)
1498 {
1499         uint id,file_version;
1500         ubyte is_multi;
1501         
1502         id = cfread_uint(fp);
1503         if (id != PLR_FILE_ID) {
1504                 Warning(LOCATION, "Player file has invalid signature");
1505                 cfclose(fp);
1506                 return -1;
1507         }
1508
1509         // check for compatibility here
1510         file_version = cfread_uint(fp);
1511         if (file_version < LOWEST_COMPATIBLE_PLAYER_FILE_VERSION) {
1512                 nprintf(("Warning", "WARNING => Player file is outdated and not compatible...\n"));
1513                 cfclose(fp);
1514                 return -1;
1515         }
1516
1517         // read in whether he's a multiplayer or not
1518         is_multi = cfread_ubyte(fp);
1519         if (is_multi)
1520                 return 1;
1521
1522         return 0;
1523 }
1524
1525 int is_pilot_multi(player *p)
1526 {
1527         return (p->flags & PLAYER_FLAGS_IS_MULTI) ? 1 : 0;
1528 }
1529
1530 // this works on barracks and player_select interface screens
1531 void init_new_pilot(player *p, int reset)
1532 {
1533         int cur_speed;
1534
1535         if (reset) {
1536                 hud_set_default_hud_config(p);          // use a default hud config
1537
1538 #ifndef MAKE_FS1
1539                 // in the demo, load up the hardcoded hcf file
1540 #ifdef FS2_DEMO
1541                 hud_config_color_load("hud_1.hcf");
1542 #else
1543                 hud_config_color_load("hud_3.hcf");
1544 #endif
1545 #else
1546                 hud_config_set_color(HUD_COLOR_GREEN);
1547 #endif
1548
1549                 control_config_reset_defaults();                // get a default keyboard config
1550                 player_set_pilot_defaults(p);                   // set up any player struct defaults
1551
1552                 cur_speed = os_config_read_uint(NULL, NOX("ComputerSpeed"), 2 );
1553                 if ( cur_speed < 0 )    {
1554                         cur_speed = 0;
1555                 } else if ( cur_speed >= NUM_DEFAULT_DETAIL_LEVELS )    {
1556                         cur_speed = NUM_DEFAULT_DETAIL_LEVELS-1;
1557                 }       
1558                 // always set to high
1559                 // DKA: 8/4/99 USE speed from registry
1560                 // cur_speed = NUM_DEFAULT_DETAIL_LEVELS-2;
1561
1562 #if NUM_DEFAULT_DETAIL_LEVELS != 4
1563 #error Code in ManagePilot assumes NUM_DEFAULT_DETAIL_LEVELS = 4
1564 #endif
1565
1566                 detail_level_set(cur_speed);
1567
1568                 Game_skill_level = game_get_default_skill_level();
1569
1570                 mprintf(( "Setting detail level to %d because of new pilot\n", cur_speed ));
1571                 Use_mouse_to_fly = 0;
1572                 Mouse_sensitivity = 4;
1573                 Joy_sensitivity = 9;
1574                 Dead_zone_size = 10;
1575         }
1576
1577         // unassigned squadron
1578         SDL_strlcpy(p->squad_name, XSTR("Unassigned", 1255), SDL_arraysize(p->squad_name));
1579         SDL_strlcpy(p->squad_filename, "", SDL_arraysize(p->squad_filename));
1580
1581         // set him to be a single player pilot by default (the actual creation routines will change this if necessary)
1582         p->flags &= ~PLAYER_FLAGS_IS_MULTI;
1583
1584         // effectively sets the length return by strlen() to 0  
1585         Campaign.filename[0] = 0;
1586         p->on_bastion = 0;
1587
1588         // pick a random pilot image for this guy
1589         if (reset){
1590                 pilot_set_random_pic(p);
1591                 p->insignia_texture = -1;
1592                 pilot_set_random_squad_pic(p);
1593         }
1594
1595         init_scoring_element(&p->stats);        
1596         
1597         p->stats.score = 0;
1598         p->stats.rank = RANK_ENSIGN;    
1599
1600 #ifndef MAKE_FS1
1601         p->tips = 1;
1602 #else
1603         p->tips = 0;
1604 #endif
1605
1606         Multi_options_g.protocol = NET_TCP;     
1607 #ifndef FS1_DEMO
1608         Multi_options_g.pxo = 1;
1609 #endif
1610
1611         // initialize default multiplayer options
1612         multi_options_set_netgame_defaults(&p->m_server_options);
1613         multi_options_set_local_defaults(&p->m_local_options);
1614
1615         Player_loadout.filename[0] = 0;
1616
1617         // reset the cutscenes which can be viewed
1618         if ( reset ){
1619                 Cutscenes_viewable = INTRO_CUTSCENE_FLAG;
1620         }
1621 }
1622
1623 void pilot_set_short_callsign(player *p, int max_width)
1624 {
1625         SDL_strlcpy(p->short_callsign, p->callsign, SDL_arraysize(p->short_callsign));
1626         gr_set_font(FONT1);
1627         gr_force_fit_string(p->short_callsign, CALLSIGN_LEN - 1, max_width);
1628         gr_get_string_size( &(p->short_callsign_width), NULL, p->short_callsign );
1629 }
1630
1631 // pick a random image for the passed player
1632 void pilot_set_random_pic(player *p)
1633 {
1634         // if there are no available pilot pics, set the image filename to null
1635         if (Num_pilot_images <= 0) {
1636                 SDL_strlcpy(p->image_filename, "", SDL_arraysize(p->image_filename));
1637         } else {
1638                 // pick a random name from the list
1639                 int random_index = rand() % Num_pilot_images;
1640                 SDL_assert((random_index >= 0) && (random_index < Num_pilot_images));
1641                 SDL_strlcpy(p->image_filename, Pilot_images_arr[random_index], SDL_arraysize(p->image_filename));
1642         }       
1643 }
1644
1645 // pick a random image for the passed player
1646 void pilot_set_random_squad_pic(player *p)
1647 {       
1648         // if there are no available pilot pics, set the image filename to null
1649         if (Num_pilot_squad_images <= 0) {
1650                 player_set_squad_bitmap(p, "");
1651                 // strcpy(p->squad_filename, "");               
1652         } else {
1653                 // pick a random name from the list
1654                 int random_index = rand() % Num_pilot_squad_images;             
1655                 SDL_assert((random_index >= 0) && (random_index < Num_pilot_squad_images));
1656                 player_set_squad_bitmap(p, Pilot_squad_images_arr[random_index]); 
1657                 // strcpy(p->squad_filename, Pilot_squad_images_arr[random_index]);
1658         }       
1659 }
1660
1661 // format a pilot's callsign into a "personal" form - ie, adding a 's or just an ' as appropriate
1662 void pilot_format_callsign_personal(const char *in_callsign, char *out_callsign, const int out_size)
1663 {
1664         // don't do anything if we've got invalid strings
1665         if((in_callsign == NULL) || (out_callsign == NULL)){
1666                 return;
1667         }
1668
1669         // copy the original string
1670         SDL_strlcpy(out_callsign, in_callsign, out_size);
1671
1672         // tack on the appropriate postfix
1673         if(in_callsign[strlen(in_callsign) - 1] == 's'){                
1674                 SDL_strlcat(out_callsign,XSTR( "\'", 45), out_size);
1675         } else {
1676                 SDL_strlcat(out_callsign,XSTR( "\'s", 46), out_size);
1677         }
1678 }
1679
1680 // throw up a popup asking the user to verify the overwrite of an existing pilot name
1681 // 1 == ok to overwrite, 0 == not ok
1682 int pilot_verify_overwrite()
1683 {
1684         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));
1685 }
1686
1687 extern int Skip_packfile_search;  // located in CFileList.cpp
1688
1689
1690 // load up the list of pilot image filenames (do this at game startup as well as barracks startup)
1691 void pilot_load_pic_list()
1692 {
1693         Num_pilot_images = 0;
1694         
1695         // load pilot images from the player images directory
1696         Num_pilot_images = cf_get_file_list_preallocated(MAX_PILOT_IMAGES, Pilot_images_arr, Pilot_image_names, CF_TYPE_PLAYER_IMAGES_MAIN, NOX("*.pcx"));
1697
1698         // sort all filenames
1699         cf_sort_filenames(Num_pilot_images, Pilot_image_names, CF_SORT_NAME);
1700 }
1701
1702 // load up the list of pilot squad filenames
1703 void pilot_load_squad_pic_list()
1704 {
1705         Num_pilot_squad_images = 0;
1706         
1707         // load pilot images from the player images directory
1708         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"));
1709
1710         // sort all filenames
1711         cf_sort_filenames(Num_pilot_squad_images, Pilot_squad_image_names, CF_SORT_NAME);
1712 }
1713
1714 // will attempt to load an insignia bitmap and set it as active for the player
1715 void player_set_squad_bitmap(player *p, const char *fname)
1716 {
1717         // sanity check
1718         if(p == NULL){
1719                 return;
1720         }
1721
1722         // if he has another bitmap already - unload it
1723         if(p->insignia_texture >= 0){
1724                 bm_unload(p->insignia_texture);
1725         }
1726
1727         p->insignia_texture = -1;
1728
1729         // try and set the new one
1730         if (fname != p->squad_filename) {
1731                 SDL_strlcpy(p->squad_filename, fname, SDL_arraysize(p->squad_filename));
1732         }
1733
1734         if(strlen(p->squad_filename) > 0){
1735                 p->insignia_texture = bm_load_duplicate(fname);
1736                 
1737                 // lock is as a transparent texture
1738                 if(p->insignia_texture != -1){
1739                         bm_lock(p->insignia_texture, 16, BMP_TEX_XPARENT);
1740                         bm_unlock(p->insignia_texture);
1741                 }
1742         }
1743
1744         /*
1745         flen = strlen(filename);
1746         elen = strlen(ext);
1747         SDL_assert(flen < MAX_PATH_LEN);
1748         strcpy(path, filename);
1749         if ((flen < 4) || SDL_strcasecmp(path + flen - elen, ext)) {
1750                 SDL_assert(flen + elen < MAX_PATH_LEN);
1751                 strcat(path, ext);
1752         }
1753         */
1754 }
1755
1756 // set squadron
1757 void player_set_squad(player *p, const char *squad_name)
1758 {
1759         // sanity check
1760         if(p == NULL){
1761                 return;
1762         }
1763
1764         SDL_strlcpy(p->squad_name, squad_name, SDL_arraysize(p->squad_name));
1765 }
1766
1767 DCF(pilot,"Changes pilot stats. (Like reset campaign)" )
1768 {
1769         if (Dc_command) {
1770                 dc_get_arg(ARG_STRING);
1771                 if (!strcmp(Dc_arg, NOX("reset"))) {
1772                         if (strlen(Campaign.filename)) {
1773                                 mission_campaign_savefile_delete(Campaign.filename);
1774                                 mission_campaign_load(Campaign.filename);
1775                         }
1776
1777                 } else {
1778                         Dc_help = 1;  // print usage, not stats
1779                 }
1780         }
1781
1782         if (Dc_help) {
1783                 dc_printf( "Usage: pilot keyword\nWhere keyword can be in the following forms:\n" );
1784                 dc_printf( "pilot reset                 Resets campaign stats.\n" );
1785                 Dc_status = 0;  // don't print status if help is printed.  Too messy.
1786         }
1787
1788         if (Dc_status) {
1789                 // no stats
1790         }
1791 }
1792
1793 struct fs_keycode_t {
1794         short fs_code;
1795         SDL_Keycode sdl_code;
1796 };
1797
1798 static const fs_keycode_t keycode_lookup[] = {
1799         { /* KEY_0 */           0x0B,   SDLK_0                  },
1800         { /* KEY_1 */           0x02,   SDLK_1                  },
1801         { /* KEY_2 */           0x03,   SDLK_2                  },
1802         { /* KEY_3 */           0x04,   SDLK_3                  },
1803         { /* KEY_4 */           0x05,   SDLK_4                  },
1804         { /* KEY_5 */           0x06,   SDLK_5                  },
1805         { /* KEY_6 */           0x07,   SDLK_6                  },
1806         { /* KEY_7 */           0x08,   SDLK_7                  },
1807         { /* KEY_8 */           0x09,   SDLK_8                  },
1808         { /* KEY_9 */           0x0A,   SDLK_9                  },
1809         { /* KEY_A */           0x1E,   SDLK_a                  },
1810         { /* KEY_B */           0x30,   SDLK_b                  },
1811         { /* KEY_C */           0x2E,   SDLK_c                  },
1812         { /* KEY_D */           0x20,   SDLK_d                  },
1813         { /* KEY_E */           0x12,   SDLK_e                  },
1814         { /* KEY_F */           0x21,   SDLK_f                  },
1815         { /* KEY_G */           0x22,   SDLK_g                  },
1816         { /* KEY_H */           0x23,   SDLK_h                  },
1817         { /* KEY_I */           0x17,   SDLK_i                  },
1818         { /* KEY_J */           0x24,   SDLK_j                  },
1819         { /* KEY_K */           0x25,   SDLK_k                  },
1820         { /* KEY_L */           0x26,   SDLK_l                  },
1821         { /* KEY_M */           0x32,   SDLK_m                  },
1822         { /* KEY_N */           0x31,   SDLK_n                  },
1823         { /* KEY_O */           0x18,   SDLK_o                  },
1824         { /* KEY_P */           0x19,   SDLK_p                  },
1825         { /* KEY_Q */           0x10,   SDLK_q                  },
1826         { /* KEY_R */           0x13,   SDLK_r                  },
1827         { /* KEY_S */           0x1F,   SDLK_s                  },
1828         { /* KEY_T */           0x14,   SDLK_t                  },
1829         { /* KEY_U */           0x16,   SDLK_u                  },
1830         { /* KEY_V */           0x2F,   SDLK_v                  },
1831         { /* KEY_W */           0x11,   SDLK_w                  },
1832         { /* KEY_X */           0x2D,   SDLK_x                  },
1833         { /* KEY_Y */           0x15,   SDLK_y                  },
1834         { /* KEY_Z */           0x2C,   SDLK_z                  },
1835         { /* KEY_MINUS */       0x0C,   SDLK_MINUS              },
1836         { /* KEY_EQUAL */       0x0D,   SDLK_EQUALS             },
1837         { /* KEY_DIVIDE */      0x35,   SDLK_SLASH              },
1838         { /* KEY_SLASH */       0x2B,   SDLK_BACKSLASH  },
1839         { /* KEY_SLASH_UK */    0x56,   SDLK_BACKSLASH  },
1840         { /* KEY_COMMA */       0x33,   SDLK_COMMA              },
1841         { /* KEY_PERIOD */      0x34,   SDLK_PERIOD             },
1842         { /* KEY_SEMICOL */     0x27,   SDLK_SEMICOLON  },
1843         { /* KEY_LBRACKET */    0x1A,   SDLK_LEFTBRACKET        },
1844         { /* KEY_RBRACKET */    0x1B,   SDLK_RIGHTBRACKET       },
1845         { /* KEY_RAPOSTRO */    0x28,   SDLK_QUOTE              },
1846         { /* KEY_LAPOSTRO */    0x29,   SDLK_BACKQUOTE  },
1847         { /* KEY_ESC */         0x01,   SDLK_ESCAPE             },
1848         { /* KEY_ENTER */       0x1C,   SDLK_RETURN             },
1849         { /* KEY_BACKSP */      0x0E,   SDLK_BACKSPACE  },
1850         { /* KEY_TAB */         0x0F,   SDLK_TAB                },
1851         { /* KEY_SPACEBAR */    0x39,   SDLK_SPACE              },
1852         { /* KEY_NUMLOCK */     0x61,   SDLK_NUMLOCKCLEAR       },
1853         { /* KEY_SCROLLOCK */   0x46,   SDLK_SCROLLLOCK },
1854         { /* KEY_CAPSLOCK */    0x3A,   SDLK_CAPSLOCK   },
1855         { /* KEY_LSHIFT */      0x2A,   SDLK_LSHIFT             },
1856         { /* KEY_RSHIFT */      0x36,   SDLK_RSHIFT             },
1857         { /* KEY_LALT */        0x38,   SDLK_LALT               },
1858         { /* KEY_RALT */        0xB8,   SDLK_RALT               },
1859         { /* KEY_LCTRL */       0x1D,   SDLK_LCTRL              },
1860         { /* KEY_RCTRL */       0x9D,   SDLK_RCTRL              },
1861         { /* KEY_F1 */          0x3B,   SDLK_F1                 },
1862         { /* KEY_F2 */          0x3C,   SDLK_F2                 },
1863         { /* KEY_F3 */          0x3D,   SDLK_F3                 },
1864         { /* KEY_F4 */          0x3E,   SDLK_F4                 },
1865         { /* KEY_F5 */          0x3F,   SDLK_F5                 },
1866         { /* KEY_F6 */          0x40,   SDLK_F6                 },
1867         { /* KEY_F7 */          0x41,   SDLK_F7                 },
1868         { /* KEY_F8 */          0x42,   SDLK_F8                 },
1869         { /* KEY_F9 */          0x43,   SDLK_F9                 },
1870         { /* KEY_F10 */         0x44,   SDLK_F10                },
1871         { /* KEY_F11 */         0x57,   SDLK_F11                },
1872         { /* KEY_F12 */         0x58,   SDLK_F12                },
1873         { /* KEY_PAD0 */        0x52,   SDLK_KP_0               },
1874         { /* KEY_PAD1 */        0x4F,   SDLK_KP_1               },
1875         { /* KEY_PAD2 */        0x50,   SDLK_KP_2               },
1876         { /* KEY_PAD3 */        0x51,   SDLK_KP_3               },
1877         { /* KEY_PAD4 */        0x4B,   SDLK_KP_4               },
1878         { /* KEY_PAD5 */        0x4C,   SDLK_KP_5               },
1879         { /* KEY_PAD6 */        0x4D,   SDLK_KP_6               },
1880         { /* KEY_PAD7 */        0x47,   SDLK_KP_7               },
1881         { /* KEY_PAD8 */        0x48,   SDLK_KP_8               },
1882         { /* KEY_PAD9 */        0x49,   SDLK_KP_9               },
1883         { /* KEY_PADMINUS */    0x4A,   SDLK_KP_MINUS   },
1884         { /* KEY_PADPLUS */     0x4E,   SDLK_KP_PLUS    },
1885         { /* KEY_PADPERIOD */   0x53,   SDLK_KP_PERIOD  },
1886         { /* KEY_PADDIVIDE */   0xB5,   SDLK_KP_DIVIDE  },
1887         { /* KEY_PADMULTIPLY */ 0x37,   SDLK_KP_MULTIPLY        },
1888         { /* KEY_PADENTER */    0x9C,   SDLK_KP_ENTER   },
1889         { /* KEY_INSERT */      0xD2,   SDLK_INSERT             },
1890         { /* KEY_HOME */        0xC7,   SDLK_HOME               },
1891         { /* KEY_PAGEUP */      0xC9,   SDLK_PAGEUP             },
1892         { /* KEY_DELETE */      0xd3,   SDLK_DELETE             },
1893         { /* KEY_END */         0xCF,   SDLK_END                },
1894         { /* KEY_PAGEDOWN */    0xD1,   SDLK_PAGEDOWN   },
1895         { /* KEY_UP */          0xC8,   SDLK_UP                 },
1896         { /* KEY_DOWN */        0xD0,   SDLK_DOWN               },
1897         { /* KEY_LEFT */        0xCB,   SDLK_LEFT               },
1898         { /* KEY_RIGHT */       0xCD,   SDLK_RIGHT              },
1899         { /* KEY_PRINT_SCRN */  0xB7,   SDLK_PRINTSCREEN        },
1900         { /* KEY_PAUSE */       0x45,   SDLK_PAUSE              },
1901         { /* KEY_BREAK */       0xc6,   SDLK_PAUSE              }
1902 };
1903
1904 static SDL_Keycode keycode_translate_from(short keycode)
1905 {
1906         const int tbl_size = sizeof(keycode_lookup) / sizeof(fs_keycode_t);
1907
1908         if (keycode < 0) {
1909                 return -1;
1910         }
1911
1912         int mods = keycode & 0xf900;
1913         keycode &= KEY_MASK;
1914
1915         for (int i = 0; i < tbl_size; i++) {
1916                 if (keycode_lookup[i].fs_code == keycode) {
1917                         return (SDL_Keycode)(keycode_lookup[i].sdl_code | mods);
1918                 }
1919         }
1920
1921         return -1;
1922 }
1923
1924 static short keycode_translate_to(SDL_Keycode keycode)
1925 {
1926         const int tbl_size = sizeof(keycode_lookup) / sizeof(fs_keycode_t);
1927
1928         if (keycode < 0) {
1929                 return -1;
1930         }
1931
1932         int mods = keycode & 0xf900;
1933         keycode &= KEY_MASK;
1934
1935         for (int i = 0; i < tbl_size; i++) {
1936                 if (keycode_lookup[i].sdl_code == keycode) {
1937                         return (short)(keycode_lookup[i].fs_code | mods);
1938                 }
1939         }
1940
1941         return -1;
1942 }