2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/Localization/localize.cpp $
17 * Revision 1.8 2005/03/29 02:18:47 taylor
18 * Various 64-bit platform fixes
19 * Fix compiler errors with MAKE_FS1 and fix gr_set_bitmap() too
20 * Make sure that turrets can fire at asteroids for FS1 (needed for a couple missions)
21 * Streaming audio support (big thanks to Pierre Willenbrock!!)
22 * Removed dependance on strings.tbl for FS1 since we don't actually need it now
24 * Revision 1.7 2004/07/04 11:39:06 taylor
25 * fix missing debrief text, crash on exit, path separator's, warning fixes, no GR_SOFT
27 * Revision 1.6 2003/06/11 18:30:32 taylor
30 * Revision 1.5 2003/06/03 04:00:40 taylor
31 * Polish language support (Janusz Dziemidowicz)
33 * Revision 1.4 2003/05/18 03:55:30 taylor
34 * automatic language selection support
36 * Revision 1.3 2002/06/09 04:41:22 relnev
37 * added copyright header
39 * Revision 1.2 2002/05/07 03:16:46 theoddone33
40 * The Great Newline Fix
42 * Revision 1.1.1.1 2002/05/03 03:28:09 root
46 * 91 6/16/00 3:16p Jefff
47 * sim of the year dvd version changes, a few german soty localization
50 * 90 11/02/99 8:20p Jefff
51 * more translation changes/additions
53 * 89 11/02/99 3:24p Jefff
54 * added translation functions for a few key instances where english was
57 * 88 10/29/99 10:40p Jefff
60 * 87 10/28/99 2:05a Jefff
61 * safety fix in localization stuff
63 * 86 10/26/99 10:53a Jefff
64 * German builds now correctly default to German on first run
66 * 85 10/25/99 11:22p Jefff
67 * mo' strings (launcher)
69 * 84 10/25/99 5:46p Jefff
70 * Many localization fixes/changes for German builds
72 * 83 10/14/99 3:25p Jefff
73 * soo...many....xstrs....
75 * 82 10/14/99 2:52p Jefff
76 * localization fixes. added support for hi-res specific xstr offsets
78 * 81 10/13/99 3:58p Jefff
79 * added a bunch of missed XSTRs
81 * 80 9/13/99 11:30a Dave
82 * Added checkboxes and functionality for disabling PXO banners as well as
83 * disabling d3d zbuffer biasing.
85 * 79 9/08/99 9:59a Jefff
87 * 78 9/07/99 4:01p Dave
88 * Fixed up a string.tbl paroblem (self destruct message). Make sure IPX
89 * does everything properly (setting up address when binding). Remove
90 * black rectangle background from UI_INPUTBOX.
92 * 77 9/07/99 1:54p Jefff
93 * skip mission strings added
95 * 76 9/03/99 10:55a Jefff
98 * 75 9/03/99 1:31a Dave
99 * CD checking by act. Added support to play 2 cutscenes in a row
100 * seamlessly. Fixed super low level cfile bug related to files in the
101 * root directory of a CD. Added cheat code to set campaign mission # in
104 * 74 8/27/99 12:04a Dave
105 * Campaign loop screen.
107 * 73 8/26/99 12:48p Jefff
109 * 72 8/22/99 5:53p Dave
110 * Scoring fixes. Added self destruct key. Put callsigns in the logfile
111 * instead of ship designations for multiplayer players.
113 * 71 8/22/99 1:55p Dave
114 * Cleaned up host/team-captain leaving code.
116 * 70 8/17/99 7:32p Jefff
119 * 69 8/16/99 5:15p Dave
120 * Upped string count.
122 * 68 8/16/99 4:04p Dave
123 * Big honking checkin.
125 * 67 8/11/99 2:09p Jefff
126 * ill give you three guesses.
128 * 66 8/10/99 7:57p Jefff
131 * 65 8/05/99 2:40p Jefff
134 * 64 8/02/99 9:13p Dave
137 * 63 8/02/99 12:01p Jefff
140 * 62 7/29/99 10:47p Dave
141 * Standardized D3D fogging using vertex fog. Shook out Savage 4 bugs.
143 * 61 7/29/99 9:44a Jefff
144 * added some strings for ingame objectives screen
146 * 60 7/27/99 7:15p Jefff
147 * 3 new interface strings
149 * 59 7/27/99 6:53p Dave
150 * Hi-res main hall support.
152 * 58 7/24/99 1:54p Dave
153 * Hud text flash gauge. Reworked dead popup to use 4 buttons in red-alert
156 * 57 7/22/99 4:08p Jefff
159 * 56 7/19/99 7:20p Dave
160 * Beam tooling. Specialized player-killed-self messages. Fixed d3d nebula
163 * 55 7/19/99 2:13p Dave
164 * Added some new strings for Heiko.
166 * 54 7/15/99 2:37p Jefff
168 * 53 7/13/99 6:07p Jefff
169 * Added support for localization string offsets.
171 * 52 7/09/99 10:32p Dave
172 * Command brief and red alert screens.
174 * 51 6/25/99 2:51p Jasons
177 * 50 6/25/99 11:59a Dave
178 * Multi options screen.
180 * 49 6/24/99 12:34a Dave
181 * Main options screen.
183 * 48 6/19/99 3:58p Dave
186 * 47 6/14/99 5:19p Dave
188 * 46 6/04/99 11:32a Dave
189 * Added reset text to ship select screen. Fixed minor xstr bug in ui
192 * 45 6/01/99 6:07p Dave
193 * New loading/pause/please wait bar.
195 * 44 6/01/99 4:03p Dave
196 * Changed some popup flag #defines. Upped string count to 1336
198 * 43 5/26/99 11:46a Dave
199 * Added ship-blasting lighting and made the randomization of lighting
200 * much more customizable.
202 * 42 5/22/99 6:05p Dave
203 * Fixed a few localization # problems.
205 * 41 5/21/99 6:45p Dave
206 * Sped up ui loading a bit. Sped up localization disk access stuff. Multi
207 * start game screen, multi password, and multi pxo-help screen.
209 * 40 5/17/99 2:52p Dave
210 * Fixed dumb localization problem.
212 * 39 5/17/99 10:07a Dave
213 * Updated string count.
215 * 38 5/04/99 6:38p Dave
216 * Finished multi join-wait screen.
218 * 37 5/04/99 5:20p Dave
219 * Fixed up multiplayer join screen and host options screen. Should both
222 * 36 5/03/99 11:04p Dave
223 * Most of the way done with the multi join screen.
225 * 35 5/03/99 8:32p Dave
226 * New version of multi host options screen.
228 * 34 4/09/99 2:28p Dave
229 * Upped Xstring count.
231 * 33 4/09/99 2:21p Dave
232 * Multiplayer beta stuff. CD checking.
234 * 32 4/08/99 12:50p Neilk
235 * changed number of xstr constant
237 * 31 4/07/99 5:54p Dave
238 * Fixes for encryption in updatelauncher.
240 * 30 3/24/99 4:05p Dave
241 * Put in support for assigning the player to a specific squadron with a
242 * specific logo. Preliminary work for doing pos/orient checksumming in
243 * multiplayer to reduce bandwidth.
245 * 29 3/23/99 3:35p Andsager
246 * Add error check for duplicate string indices
248 * 28 3/22/99 2:56p Andsager
249 * Localize Launcher Updater
251 * 27 2/23/99 11:18a Andsager
252 * Localize launcher using strings.tbl
254 * 26 2/22/99 9:35p Andsager
255 * Add lcl_get_language_name() returns string with current lang. Added
256 * localization for launcher.
258 * 25 2/21/99 6:02p Dave
259 * Fixed standalone WSS packets.
261 * 24 2/17/99 2:11p Dave
262 * First full run of squad war. All freespace and tracker side stuff
265 * 23 2/12/99 6:15p Dave
266 * Pre-mission Squad War code is 95% done.
268 * 22 2/02/99 7:27p Neilk
269 * implemented lc_close() to free up some string memory when shutting down
271 * 21 1/30/99 1:29a Dave
272 * Fixed nebula thumbnail problem. Full support for 1024x768 choose pilot
273 * screen. Fixed beam weapon death messages.
275 * 20 1/29/99 7:56p Neilk
276 * Added new mission log interface
278 * 19 1/29/99 4:17p Dave
279 * New interface screens.
281 * 18 1/29/99 12:47a Dave
282 * Put in sounds for beam weapon. A bunch of interface screens (tech
285 * 17 1/13/99 2:10p Andsager
288 * 16 12/14/98 12:13p Dave
289 * Spiffed up xfer system a bit. Put in support for squad logo file xfer.
292 * 15 12/03/98 5:22p Dave
293 * Ported over Freespace 1 multiplayer ships.tbl and weapons.tbl
296 * 14 12/02/98 5:47p Dave
297 * Put in interface xstr code. Converted barracks screen to new format.
299 * 13 12/01/98 4:46p Dave
300 * Put in targa bitmap support (16 bit).
308 #include "localize.h"
310 #include "osregistry.h"
314 // ------------------------------------------------------------------------------------------------------------
315 // LOCALIZE DEFINES/VARS
318 // general language/localization data ---------------------
321 int Lcl_current_lang = LCL_ENGLISH;
323 // language info table
324 typedef struct lang_info {
325 char lang_name[LCL_LANG_NAME_LEN + 1]; // literal name of the language
326 char lang_ext[LCL_LANG_NAME_LEN + 1]; // for adding to names on disk access
329 lang_info Lcl_languages[LCL_NUM_LANGUAGES] = {
330 { "English", "" }, // english
331 { "German", "gr" }, // german
332 { "French", "fr" }, // french
333 { "Polish", "pl" }, // polish
336 //#if defined(GERMAN_BUILD)
337 //#define DEFAULT_LANGUAGE "German"
339 #define DEFAULT_LANGUAGE "English"
342 // following is the offset where special characters start in our font
343 #define LCL_SPECIAL_CHARS_FR 164
344 #define LCL_SPECIAL_CHARS_GR 164
345 #define LCL_SPECIAL_CHARS_PL 127
346 #define LCL_SPECIAL_CHARS 127
347 int Lcl_special_chars;
349 // use these to replace *_BUILD, values before
350 // only 1 will be active at a time
357 // executable string localization data --------------------
359 // XSTR_SIZE is the total count of unique XSTR index numbers. An index is used to
360 // reference an entry in strings.tbl. This is used for translation of strings from
361 // the english version (in the code) to a foreign version (in the table). Thus, if you
362 // add a new string to the code, you must assign it a new index. Use the number below for
363 // that index and increase the number below by one.
364 #define XSTR_SIZE 1570
367 // struct to allow for strings.tbl-determined x offset
368 // offset is 0 for english, by default
371 int offset_x; // string offset in 640
372 int offset_x_hi; // string offset in 1024
375 //char *Xstr_table[XSTR_SIZE];
376 lcl_xstr Xstr_table[XSTR_SIZE];
380 // table/mission externalization stuff --------------------
382 #define TABLE_STRING_FILENAME "tstrings.tbl"
383 // filename of the file to use when localizing table strings
384 char *Lcl_ext_filename = NULL;
385 CFILE *Lcl_ext_file = NULL;
387 // for scanning/parsing tstrings.tbl (from ExStr)
388 #define PARSE_TEXT_STRING_LEN 2048
389 #define PARSE_ID_STRING_LEN 128
390 #define TS_SCANNING 0 // scanning for a line of text
391 #define TS_ID_STRING 1 // reading in an id string
392 #define TS_OPEN_QUOTE 2 // looking for an open quote
393 #define TS_STRING 3 // reading in the text string itself
394 int Ts_current_state = 0;
395 char Ts_text[PARSE_TEXT_STRING_LEN]; // string we're currently working with
396 char Ts_id_text[PARSE_ID_STRING_LEN]; // id string we're currently working with
400 // file pointers for optimized string lookups
401 // some example times for Freespace2 startup with granularities (mostly .tbl files, ~500 strings in the table file, many looked up more than once)
402 // granularity 20 : 13 secs
403 // granularity 10 : 11 secs
404 // granularity 5 : 9 secs
405 // granularity 2 : 7-8 secs
406 #define LCL_GRANULARITY 1 // how many strings between each pointer (lower granularities should give faster lookup times)
407 #define LCL_MAX_POINTERS 4500 // max # of pointers
408 #define LCL_MAX_STRINGS (LCL_GRANULARITY * LCL_MAX_POINTERS)
409 int Lcl_pointers[LCL_MAX_POINTERS];
410 int Lcl_pointer_count = 0;
413 // ------------------------------------------------------------------------------------------------------------
414 // LOCALIZE FORWARD DECLARATIONS
417 // associate table file externalization with the specified input file
418 void lcl_ext_associate(char *filename);
420 // given a valid XSTR() tag piece of text, extract the string portion, return it in out, nonzero on success
421 int lcl_ext_get_text(char *xstr, char *out);
423 // given a valid XSTR() tag piece of text, extract the id# portion, return the value in out, nonzero on success
424 int lcl_ext_get_id(char *xstr, int *out);
426 // given a valid XSTR() id#, lookup the string in tstrings.tbl, filling in out if found, nonzero on success
427 int lcl_ext_lookup(char *out, int id);
429 // if the char is a valid char for a signed integer value string
430 int lcl_is_valid_numeric_char(char c);
432 // sub-parse function for individual lines of tstrings.tbl (from Exstr)
433 // returns : integer with the low bits having the following values :
434 // 0 on fail, 1 on success, 2 if found a matching id/string pair, 3 if end of language has been found
435 // for cases 1 and 2 : the high bit (1<<31) will be set if the parser detected the beginning of a new string id on this line
436 // so be sure to mask this value out to get the low portion of the return value
438 int lcl_ext_lookup_sub(char *text, char *out, int id);
440 // initialize the pointer array into tstrings.tbl (call from lcl_ext_open() ONLY)
441 void lcl_ext_setup_pointers();
444 // ------------------------------------------------------------------------------------------------------------
445 // LOCALIZE FUNCTIONS
448 // initialize localization, if no language is passed - use the language specified in the registry
449 void lcl_init(int lang_init)
451 char lang_string[128];
455 // initialize encryption
458 // read the language from the registry
460 memset(lang_string, 0, 128);
462 // default to DEFAULT_LANGUAGE (which should be english so we dont have to put German text
463 // in tstrings in the #default section
464 ret = os_config_read_string(NULL, "Language", DEFAULT_LANGUAGE);
468 strcpy(lang_string, DEFAULT_LANGUAGE);
470 strcpy(lang_string, ret);
475 for(idx=0; idx<LCL_NUM_LANGUAGES; idx++){
476 if(!stricmp(Lcl_languages[idx].lang_name, lang_string)){
485 Assert((lang_init >= 0) && (lang_init < LCL_NUM_LANGUAGES));
490 Lcl_pointer_count = 0;
492 // associate the table string file
493 lcl_ext_associate(TABLE_STRING_FILENAME);
495 // set the language (this function takes care of setting up file pointers)
496 lcl_set_language(lang);
499 // added 2.2.99 by NeilK to take care of fs2 launcher memory leaks
500 // shutdown localization
505 // if the filename exists, free it up
506 if(Lcl_ext_filename != NULL){
507 free(Lcl_ext_filename);
510 // free the Xstr_table
511 for (i=0; i<XSTR_SIZE; i++) {
512 if (Xstr_table[i].str != NULL) {
513 free(Xstr_table[i].str);
518 // determine what language we're running in, see LCL_* defines above
519 int lcl_get_language()
521 return Lcl_current_lang;
524 // initialize the xstr table
530 char language_tag[512];
532 char *p_offset = NULL;
533 int offset_lo = 0, offset_hi = 0;
536 for (i=0; i<XSTR_SIZE; i++){
537 Xstr_table[i].str = NULL;
541 if ((rval = setjmp(parse_abort)) != 0) {
542 mprintf(("Error parsing 'strings.tbl'\nError code = %i.\n", rval));
544 // make sure localization is NOT running
547 read_file_text("strings.tbl");
550 // move down to the proper section
551 memset(language_tag, 0, 512);
552 strcpy(language_tag, "#");
553 strcat(language_tag, Lcl_languages[Lcl_current_lang].lang_name);
554 if(skip_to_string(language_tag) != 1){
555 Error(LOCATION, NOX("Strings.tbl is corrupt"));
558 // parse all the strings in this section of the table
559 while (!check_for_string("#")) {
560 int num_offsets_on_this_line = 0;
563 stuff_string(buf, F_NAME, NULL, 4096);
571 if (!isspace(buf[i])) {
576 // trim unneccesary end of string
578 // Assert(buf[i] == '"');
580 // probably an offset on this entry
581 buf[i+1] = 0; // drop down a null terminator (prolly unnecessary)
582 while(!is_white_space(buf[i])) i--; // back up over the potential offset
583 while(is_white_space(buf[i])) i--; // now back up over intervening spaces
584 num_offsets_on_this_line = 1;
586 // could have a 2nd offset value (one for 640, one for 1024)
588 while(!is_white_space(buf[i])) i--; // back up over the 2nd offset
589 while(is_white_space(buf[i])) i--; // now back up over intervening spaces
590 num_offsets_on_this_line = 2;
593 p_offset = &buf[i+1]; // get ptr to string section with offset in it
595 Error(LOCATION, NOX("Strings.tbl is corrupt")); // now its an error
602 // copy string into buf
604 for (i=1; buf[i]; i++) {
610 } else if (chr == 'r') {
619 // null terminator on buf
622 // write into Xstr_table
623 if (index >= 0 && index < XSTR_SIZE) {
624 if (Xstr_table[index].str != NULL) {
625 Warning(LOCATION, "Strings table index %d used more than once", index);
627 Xstr_table[index].str = strdup(buf);
630 // read offset information, assume 0 if nonexistant
631 if (p_offset != NULL) {
632 if (sscanf(p_offset, "%d%d", &offset_lo, &offset_hi) < num_offsets_on_this_line) {
633 // whatever is in the file ain't a proper offset
634 Error(LOCATION, NOX("Strings.tbl is corrupt"));
637 Xstr_table[index].offset_x = offset_lo;
638 if (num_offsets_on_this_line == 2) {
639 Xstr_table[index].offset_x_hi = offset_hi;
641 Xstr_table[index].offset_x_hi = offset_lo;
644 // clear out our vars
648 num_offsets_on_this_line = 0;
658 void lcl_xstr_close()
660 for (int i=0; i<XSTR_SIZE; i++){
661 if (Xstr_table[i].str != NULL) {
662 free(Xstr_table[i].str);
663 Xstr_table[i].str = NULL;
669 // set our current language
670 void lcl_set_language(int lang)
672 Lcl_current_lang = lang;
674 nprintf(("General", "Setting language to %s\n", Lcl_languages[lang].lang_name));
676 // flag the proper language as being active
684 Lcl_special_chars = LCL_SPECIAL_CHARS;
688 Lcl_special_chars = LCL_SPECIAL_CHARS_FR;
692 Lcl_special_chars = LCL_SPECIAL_CHARS_GR;
696 Lcl_special_chars = LCL_SPECIAL_CHARS_PL;
700 // set to 0, so lcl_ext_open() knows to reset file pointers
701 Lcl_pointer_count = 0;
703 // reset file pointers to the proper language-section
704 if(Lcl_current_lang != LCL_DEFAULT_LANGUAGE){
705 lcl_ext_setup_pointers();
709 // maybe add on an appropriate subdirectory when opening a localized file
710 void lcl_add_dir(char *current_path)
715 // if the disk extension is 0 length, don't add enything
716 if (strlen(Lcl_languages[Lcl_current_lang].lang_ext) <= 0) {
720 // get the length of the string so far
721 path_len = strlen(current_path);
726 // get the current last char
727 last_char = current_path[path_len - 1];
729 // if the last char is a slash, just copy in the disk extension
730 if (last_char == DIR_SEPARATOR_CHAR) {
731 strcat(current_path, Lcl_languages[Lcl_current_lang].lang_ext);
732 strcat(current_path, DIR_SEPARATOR_STR);
734 // otherwise add a slash, then copy in the disk extension
736 strcat(current_path, DIR_SEPARATOR_STR);
737 strcat(current_path, Lcl_languages[Lcl_current_lang].lang_ext);
741 // maybe add localized directory to full path with file name when opening a localized file
742 void lcl_add_dir_to_path_with_filename(char *current_path)
744 char temp[MAX_PATH_LEN];
746 // if the disk extension is 0 length, don't add enything
747 if (strlen(Lcl_languages[Lcl_current_lang].lang_ext) <= 0) {
751 // find position of last slash and copy rest of filename (not counting slash) to temp
752 // mark end of current path with '\0', so strcat will work
753 char *last_slash = strrchr(current_path, DIR_SEPARATOR_CHAR);
754 if (last_slash == NULL) {
755 strcpy(temp, current_path);
756 current_path[0] = '\0';
758 strcpy(temp, last_slash+1);
759 last_slash[1] = '\0';
763 strcat(current_path, Lcl_languages[Lcl_current_lang].lang_ext);
764 strcat(current_path, DIR_SEPARATOR_STR);
766 // copy rest of filename from temp
767 strcat(current_path, temp);
771 // externalization of table/mission files -----------------------
773 // open the externalization file for use during parsing (call before parsing a given file)
776 // if the file is already open, do nothing
777 Assert(Lcl_ext_file == NULL);
779 // if we're running in the default language, do nothing
780 if(Lcl_current_lang == LCL_DEFAULT_LANGUAGE){
784 // otherwise open the file
785 Lcl_ext_file = cfopen(Lcl_ext_filename, "rt");
786 if(Lcl_ext_file == NULL){
791 // close the externalization file (call after parsing a given file)
794 // if the file is not open, do nothing
795 if(Lcl_ext_file == NULL){
799 // if we're running in the default language, do nothing
800 if(Lcl_current_lang == LCL_DEFAULT_LANGUAGE){
804 // otherwise close it
805 cfclose(Lcl_ext_file);
809 // get the localized version of the string. if none exists, return the original string
810 // valid input to this function includes :
811 // "this is some text"
812 // XSTR("wheeee", -1)
814 // and these should cover all the externalized string cases
815 // fills in id if non-NULL. a value of -2 indicates it is not an external string
816 void lcl_ext_localize(char *in, char *out, int max_len, int *id)
819 char text_str[2048]="";
820 char lookup_str[2048]="";
827 // default (non-external string) value
832 str_len = strlen(in);
834 // if the string is < 9 chars, it can't be an XSTR("",) tag, so just copy it
836 if(str_len > max_len){
837 error_display(0, "Token too long: [%s]. Length = %i. Max is %i.\n", in, str_len, max_len);
847 // otherwise, check to see if it's an XSTR() tag
848 memset(first_four, 0, 5);
849 strncpy(first_four, in, 4);
850 if(stricmp(first_four, "XSTR")){
852 if(str_len > max_len){
853 error_display(0, "Token too long: [%s]. Length = %i. Max is %i.\n", in, str_len, max_len);
863 // at this point we _know_ its an XSTR() tag, so split off the strings and id sections
864 if(!lcl_ext_get_text(in, text_str)){
872 if(!lcl_ext_get_id(in, &str_id)){
880 // if the localization file is not open, or we're running in the default language, return the original string
881 if((Lcl_ext_file == NULL) || (str_id < 0) || (Lcl_current_lang == LCL_DEFAULT_LANGUAGE)){
882 strcpy(out, text_str);
889 // attempt to find the string
890 if(lcl_ext_lookup(lookup_str, str_id)){
891 // copy to the outgoing string
892 Assert(strlen(lookup_str) <= (unsigned int)(max_len - 1));
894 if (strlen(lookup_str) > (unsigned int)(max_len-1)) {
895 // be safe and truncate string to fit
896 strncpy(out, lookup_str, (size_t) (max_len-1));
897 out[max_len-1] = '\0'; // ensure null terminator, since strncpy(...) doesnt.
899 strcpy(out, lookup_str);
903 // otherwise use what we have - probably should Int3() or assert here
905 strcpy(out, text_str);
914 // translate the specified string based upon the current language
915 char *XSTR(char *str, int index)
922 if (index >= 0 && index < XSTR_SIZE) {
923 if (Xstr_table[index].str){
924 return Xstr_table[index].str; // return translation of string
928 // can't translate, return original english string
932 // retrieve the offset for a localized string
933 int lcl_get_xstr_offset(int index, int res)
936 return Xstr_table[index].offset_x;
938 return Xstr_table[index].offset_x_hi;
943 // ------------------------------------------------------------------------------------------------------------
944 // LOCALIZE FORWARD DEFINITIONS
947 // associate table file externalization with the specified input file
948 void lcl_ext_associate(char *filename)
950 // if the filename already exists, free it up
951 if(Lcl_ext_filename != NULL){
952 free(Lcl_ext_filename);
955 // set the new filename
956 Lcl_ext_filename = strdup(filename);
959 // given a valid XSTR() tag piece of text, extract the string portion, return it in out, nonzero on success
960 int lcl_ext_get_text(char *xstr, char *out)
962 int str_start, str_end;
966 Assert(xstr != NULL);
968 str_len = strlen(xstr);
970 // this is some crazy wack-ass code.
971 // look for the open quote
972 str_start = str_end = 0;
973 p = strstr(xstr, "\"");
975 error_display(0, "Error parsing XSTR() tag %s\n", xstr);
978 str_start = p - xstr + 1;
980 // make sure we're not about to walk past the end of the string
981 if((p - xstr) >= str_len){
982 error_display(0, "Error parsing XSTR() tag %s\n", xstr);
986 // look for the close quote
987 p2 = strstr(p+1, "\"");
989 error_display(0, "Error parsing XSTR() tag %s\n", xstr);
995 // now that we know the boundaries of the actual string in the XSTR() tag. copy it
996 memcpy(out, xstr + str_start, str_end - str_start);
1006 // given a valid XSTR() tag piece of text, extract the id# portion, return the value in out, nonzero on success
1007 int lcl_ext_get_id(char *xstr, int *out)
1012 Assert(xstr != NULL);
1013 Assert(out != NULL);
1015 str_len = strlen(xstr);
1017 // find the first quote
1018 p = strstr(xstr, "\"");
1020 error_display(0, "Error parsing id# in XSTR() tag %s\n", xstr);
1023 // make sure we're not about to walk off the end of the string
1024 if((p - xstr) >= str_len){
1025 error_display(0, "Error parsing id# in XSTR() tag %s\n", xstr);
1030 // continue searching until we find the close quote
1032 pnext = strstr(p, "\"");
1034 error_display(0, "Error parsing id# in XSTR() tag %s\n", xstr);
1038 // if the previous char is a \, we know its not the "end-of-string" quote
1039 if(*(pnext - 1) != '\\'){
1048 // search until we find a ,
1049 pnext = strstr(p, ",");
1051 error_display(0, "Error parsing id# in XSTR() tag %s\n", xstr);
1054 // make sure we're not about to walk off the end of the string
1055 if((pnext - xstr) >= str_len){
1056 error_display(0, "Error parsing id# in XSTR() tag %s\n", xstr);
1061 // now get the id string
1063 pnext = strtok(p, ")");
1065 error_display(0, "Error parsing id# in XSTR() tag %s\n", xstr);
1069 // get the value and we're done
1076 // given a valid XSTR() id#, lookup the string in tstrings.tbl, filling in out if found, nonzero on success
1077 int lcl_ext_lookup(char *out, int id)
1083 Assert(Lcl_pointer_count >= 0);
1084 Assert(Lcl_pointers[0] >= 0);
1085 Assert(Lcl_pointers[Lcl_pointer_count - 1] >= 0);
1086 Assert(Lcl_ext_file != NULL);
1089 // seek to the closest pointer <= the id# we're looking for
1090 pointer = id / LCL_GRANULARITY;
1091 cfseek(Lcl_ext_file, Lcl_pointers[pointer], CF_SEEK_SET);
1093 // reset parsing vars and go to town
1094 Ts_current_state = TS_SCANNING;
1095 Ts_id_text_size = 0;
1097 memset(Ts_text, 0, PARSE_TEXT_STRING_LEN);
1098 memset(Ts_id_text, 0, PARSE_ID_STRING_LEN);
1099 while((cftell(Lcl_ext_file) < Lcl_pointers[Lcl_pointer_count - 1]) && cfgets(text, 1024, Lcl_ext_file)){
1100 ret = lcl_ext_lookup_sub(text, out, id);
1102 // run the line parse function
1103 switch(ret & 0x0fffffff){
1106 Int3(); // should never get here - it means the string doens't exist in the table!!
1109 // success parsing the line - please continue
1113 // found a matching string/id pair
1117 // this is because tstrings.tbl reads in as ANSI for some reason
1118 // opening tstrings with "rb" mode didnt seem to help, so its now still "rt" like before
1119 lcl_fix_umlauts(out, LCL_TO_ASCII);
1123 // end of language found
1125 Int3(); // should never get here - it means the string doens't exist in the table!!
1130 Int3(); // should never get here - it means the string doens't exist in the table!!
1134 // sub-parse function for individual lines of tstrings.tbl (from Exstr)
1135 // returns : integer with the low bits having the following values :
1136 // 0 on fail, 1 on success, 2 if found a matching id/string pair, 3 if end of language has been found
1137 // for cases 1 and 2 : the high bit (1<<31) will be set if the parser detected the beginning of a new string id on this line
1139 int lcl_ext_lookup_sub(char *text, char *out, int id)
1141 char *front; // front of the line
1142 char *p; // current ptr
1143 int len = strlen(text);
1145 char text_copy[1024];
1147 int found_new_string_id = 0;
1153 // do something useful
1154 switch(Ts_current_state){
1155 // scanning for a line of text
1157 // if the first word is #end, we're done with the file altogether
1158 strcpy(text_copy, text);
1159 tok = strtok(text_copy, " \n");
1160 if((tok != NULL) && !stricmp(tok, "#end")){
1163 // if its a commented line, skip it
1164 else if((text[0] == ';') || (text[0] == ' ') || (text[0] == '\n')){
1167 // otherwise we should have an ID #, so stuff it and move to the proper state
1169 if(lcl_is_valid_numeric_char(*p)){
1170 memset(Ts_id_text, 0, PARSE_ID_STRING_LEN);
1171 Ts_id_text_size = 0;
1172 Ts_id_text[Ts_id_text_size++] = *p;
1173 Ts_current_state = TS_ID_STRING;
1175 found_new_string_id = 1;
1185 // scanning in an id string
1187 // if we have another valid char
1188 if(lcl_is_valid_numeric_char(*p)){
1189 Ts_id_text[Ts_id_text_size++] = *p;
1191 // if we found a comma, our id# is finished, look for the open quote
1193 Ts_current_state = TS_OPEN_QUOTE;
1201 // valid space or an open quote
1202 if((*p == ' ') || (*p == '\"')){
1204 memset(Ts_text, 0, PARSE_TEXT_STRING_LEN);
1206 Ts_current_state = TS_STRING;
1215 // if we have an end quote, we need to look for a comma
1216 if((*p == '\"') /*&& (Ts_text_size > 0)*/ && (Ts_text[Ts_text_size - 1] != '\\')){
1217 // we're now done - we have a string
1218 Ts_current_state = TS_SCANNING;
1220 // if the id#'s match, copy the string and return "string found"
1221 if((atoi(Ts_id_text) == id) && (out != NULL)){
1222 strcpy(out, Ts_text);
1224 return found_new_string_id ? (1<<1) | (1<<31) : (1<<1);
1227 // otherwise, just continue parsing
1228 return found_new_string_id ? (1<<0) | (1<<31) : (1<<0);
1230 // otherwise add to the string
1232 Ts_text[Ts_text_size++] = *p;
1237 // if we have a newline, return success, we're done with this line
1239 return found_new_string_id ? (1<<0) | (1<<31) : (1<<0);
1242 // next char in the line
1248 return found_new_string_id ? (1<<0) | (1<<31) : (1<<0);
1251 // if the char is a valid char for a signed integer value
1252 int lcl_is_valid_numeric_char(char c)
1254 return ( (c == '-') || (c == '0') || (c == '1') || (c == '2') || (c == '3') || (c == '4') ||
1255 (c == '5') || (c == '6') || (c == '7') || (c == '8') || (c == '9') ) ? 1 : 0;
1258 // initialize the pointer array into tstrings.tbl (call from lcl_ext_open() ONLY)
1259 void lcl_ext_setup_pointers()
1261 char language_string[128];
1266 int found_start = 0;
1268 // open the localization file
1270 if(Lcl_ext_file == NULL){
1271 error_display(0, "Error opening externalization file! File likely does not exist or could not be found");
1275 // seek to the currently active language
1276 memset(language_string, 0, 128);
1277 strcpy(language_string, "#");
1278 if(!stricmp(DEFAULT_LANGUAGE, Lcl_languages[Lcl_current_lang].lang_name)){
1279 strcat(language_string, "default");
1281 strcat(language_string, Lcl_languages[Lcl_current_lang].lang_name);
1283 memset(line, 0, 1024);
1285 // reset seek variables and begin
1286 Lcl_pointer_count = 0;
1287 while(cfgets(line, 1024, Lcl_ext_file)){
1288 tok = strtok(line, " \n");
1293 // if the language matches, we're good to start parsing strings
1294 if(!stricmp(language_string, tok)){
1300 // if we didn't find the language specified, error
1301 if(found_start <= 0){
1302 error_display(0, "Could not find specified langauge in tstrings.tbl!\n");
1308 while(cfgets(line, 1024, Lcl_ext_file)){
1309 ret = lcl_ext_lookup_sub(line, NULL, -1);
1312 switch(ret & 0x0fffffff){
1318 // end of language found
1320 // mark one final pointer
1321 Lcl_pointers[Lcl_pointer_count++] = cftell(Lcl_ext_file) - strlen(line) - 1;
1326 // the only other case we care about is the beginning of a new id#
1328 if((string_count % LCL_GRANULARITY) == 0){
1329 // mark the pointer down
1330 Lcl_pointers[Lcl_pointer_count++] = cftell(Lcl_ext_file) - strlen(line) - 1;
1332 // if we're out of pointer slots
1333 if(Lcl_pointer_count >= LCL_MAX_POINTERS){
1334 error_display(0, "Out of pointer for tstrings.tbl lookup. Please increment LCL_MAX_POINTERS in localize.cpp");
1339 // increment string count
1344 // should never get here. we should always be exiting through case 3 (end of language section) of the above switch
1350 void lcl_get_language_name(char *lang_name)
1352 Assert(LCL_NUM_LANGUAGES == 3);
1354 strcpy(lang_name, Lcl_languages[Lcl_current_lang].lang_name);
1357 // converts german umlauted chars from ASCII to ANSI
1358 // so they appear in the launcher
1359 // how friggin lame is this
1360 // pass in a null terminated string, foo!
1361 // returns ptr to string you sent in
1362 char* lcl_fix_umlauts(char *str, int which_way)
1366 if (which_way == LCL_TO_ANSI) {
1367 // moving to ANSI charset
1368 // run thru string and perform appropriate conversions
1369 while (str[i] != '\0') {
1396 // beta-lookin thing that means "ss"
1404 // moving to ASCII charset
1405 // run thru string and perform appropriate conversions
1406 while (str[i] != '\0') {
1433 // beta-lookin thing that means "ss"
1445 // convert some of the polish characters
1446 void lcl_fix_polish(char *str)
1449 if(*str == '\xA2') *str = '\xF3';
1450 else if(*str == '\x88') *str = '\xEA';
1454 // ------------------------------------------------------------------
1455 // lcl_translate_wep_name()
1457 // For displaying weapon names in german version
1458 // since we cant actually just change them outright.
1460 void lcl_translate_wep_name(char *name)
1462 if (!strcmp(name, "Morning Star")) {
1463 strcpy(name, "Morgenstern");
1464 } else if (!strcmp(name, "MorningStar")) {
1465 strcpy(name, "Morgenstern D");
1466 } else if (!strcmp(name, "UD-8 Kayser")) {
1467 strcpy(name, "Kayserstrahl");
1468 } else if (!strcmp(name, "UD-D Kayser")) {
1469 strcpy(name, "Kayserstrahl");
1473 // ------------------------------------------------------------------
1474 // lcl_translate_brief_icon_name()
1476 // For displaying ship names in german version
1477 // since we cant actually just change them outright.
1479 void lcl_translate_brief_icon_name(char *name)
1484 if (!stricmp(name, "Subspace Portal")) {
1485 strcpy(name, "Subraum Portal");
1487 } else if (!stricmp(name, "Alpha Wing")) {
1488 strcpy(name, "Alpha");
1490 } else if (!stricmp(name, "Beta Wing")) {
1491 strcpy(name, "Beta");
1493 } else if (!stricmp(name, "Zeta Wing")) {
1494 strcpy(name, "Zeta");
1496 } else if (!stricmp(name, "Capella Node")) {
1497 strcpy(name, "Capella");
1499 } else if (!stricmp(name, "Hostile")) {
1500 strcpy(name, "Gegner");
1502 } else if (!stricmp(name, "Hostile Craft")) {
1503 strcpy(name, "Gegner");
1505 } else if (!stricmp(name, "Rebel Wing")) {
1506 strcpy(name, "Rebellen");
1508 } else if (!stricmp(name, "Rebel Fleet")) {
1509 strcpy(name, "Rebellenflotte");
1511 } else if (!stricmp(name, "Sentry Gun")) {
1512 strcpy(name, "Gesch\x81tz");
1514 } else if (!stricmp(name, "Cargo")) {
1515 strcpy(name, "Fracht");
1517 } else if (!stricmp(name, "Knossos Device")) {
1518 strcpy(name, "Knossosger\x84t");
1520 } else if (!stricmp(name, "Support")) {
1521 strcpy(name, "Versorger");
1523 } else if (!stricmp(name, "Unknown")) {
1524 strcpy(name, "Unbekannt");
1526 } else if (!stricmp(name, "Instructor")) {
1527 strcpy(name, "Ausbilder");
1529 } else if (!stricmp(name, "Jump Node")) {
1530 strcpy(name, "Sprungknoten");
1532 } else if (!stricmp(name, "Escort")) {
1533 strcpy(name, "Geleitschutz");
1535 } else if (!stricmp(name, "Asteroid Field")) {
1536 strcpy(name, "Asteroidenfeld");
1538 } else if (!stricmp(name, "Enif Station")) {
1539 strcpy(name, "Station Enif");
1541 } else if (!stricmp(name, "Rally Point")) {
1542 strcpy(name, "Sammelpunkt");
1544 } else if ((pos = strstr(name, "Transport")) != NULL) {
1545 pos += 9; // strlen of "transport"
1546 strcpy(buf, "Transporter");
1550 } else if ((pos = strstr(name, "Jump Node")) != NULL) {
1551 pos += 9; // strlen of "jump node"
1552 strcpy(buf, "Sprungknoten");
1556 } else if (!stricmp(name, "Orion under repair")) {
1557 strcpy(name, "Orion wird repariert");
1559 // SOTY-specific ones below!
1561 } else if (!stricmp(name, "Wayfarer Station")) {
1562 strcpy(name, "Station Wayfarer");
1563 } else if (!stricmp(name, "Enemy")) {
1564 strcpy(name, "Gegner");
1565 } else if (!stricmp(name, "Supply Depot")) {
1566 strcpy(name, "Nachschubdepot");
1567 } else if (!stricmp(name, "Fighter Escort")) {
1568 strcpy(name, "Jagdschutz");
1569 } else if (!stricmp(name, "Shivans")) {
1570 strcpy(name, "Shivaner");
1571 } else if (!stricmp(name, "NTF Base of Operations")) {
1572 strcpy(name, "NTF-Operationsbasis");
1573 } else if (!stricmp(name, "NTF Bombers")) {
1574 strcpy(name, "NTF-Bomber");
1575 } else if (!stricmp(name, "NTF Fighters")) {
1576 strcpy(name, "NTF-J\x84ger");
1577 } else if (!stricmp(name, "Sentry")) {
1578 strcpy(name, "Sperrgesch\x81tz");
1579 } else if (!stricmp(name, "Cargo Containers")) {
1580 strcpy(name, "Frachtbeh\x84lter");
1581 } else if (!stricmp(name, "NTF Reinforcements")) {
1582 strcpy(name, "NTF-Verst\x84rkungen");
1583 } else if (!stricmp(name, "NTF Base")) {
1584 strcpy(name, "NTF-St\x81tzpunkt");
1585 } else if (!stricmp(name, "Refugee Convoy")) {
1586 strcpy(name, "Fl\x81""chtlingskonvoi");
1587 } else if (!stricmp(name, "Food Convoy")) {
1588 strcpy(name, "Nachschubkonvoi");
1589 } else if (!stricmp(name, "Governor's Shuttle")) {
1590 strcpy(name, "F\x84hre des Gouverneurs");
1591 } else if (!stricmp(name, "GTVA Patrol")) {
1592 strcpy(name, "GTVA-Patrouille");
1593 } else if (!stricmp(name, "Escort fighters")) {
1594 strcpy(name, "Geleitschutz");
1595 } else if (!stricmp(name, "Nagada Outpost")) {
1596 strcpy(name, "Nagada-Aussenposten");
1597 } else if (!stricmp(name, "Fighters")) {
1598 strcpy(name, "J\x84ger");
1599 } else if (!stricmp(name, "Bombers")) {
1600 strcpy(name, "Bomber");
1601 } else if (!stricmp(name, "Enemy Destroyers")) {
1602 strcpy(name, "Feindliche Zerst\x94rer");
1603 } else if (!stricmp(name, "Ross 128 System")) {
1604 strcpy(name, "System Ross 128");
1605 } else if (!stricmp(name, "Knossos Station")) {
1606 strcpy(name, "Knossos-Station");
1607 } else if (!stricmp(name, "Transporters")) {
1608 strcpy(name, "Transporter");
1609 } else if (!stricmp(name, "Pirates?")) {
1610 strcpy(name, "Piraten?");
1611 } else if (!stricmp(name, "Escorts")) {
1612 strcpy(name, "Geleitschutz");
1613 } else if (!stricmp(name, "Shivan Fighters")) {
1614 strcpy(name, "J\x84ger");
1615 } else if (!stricmp(name, "Shivan Territory")) {
1616 strcpy(name, "Shivaner");
1620 // ------------------------------------------------------------------
1621 // lcl_translate_brief_icon_name_pl()
1623 // For displaying ship names in polish version
1624 // since we cant actually just change them outright.
1626 void lcl_translate_brief_icon_name_pl(char *name)
1631 if (!stricmp(name, "Subspace Portal")) {
1632 strcpy(name, "Portal podprz.");
1634 } else if (!stricmp(name, "Alpha Wing")) {
1635 strcpy(name, "Alfa");
1637 } else if (!stricmp(name, "Beta Wing")) {
1638 strcpy(name, "Beta");
1640 } else if (!stricmp(name, "Zeta Wing")) {
1641 strcpy(name, "Zeta");
1643 } else if (!stricmp(name, "Capella Node")) {
1644 strcpy(name, "Capella");
1646 } else if (!stricmp(name, "Hostile")) {
1647 strcpy(name, "Wr\xF3g");
1649 } else if (!stricmp(name, "Hostile Craft")) {
1650 strcpy(name, "Wr\xF3g");
1652 } else if (!stricmp(name, "Rebel Wing")) {
1653 strcpy(name, "Rebelianci");
1655 } else if (!stricmp(name, "Rebel Fleet")) {
1656 strcpy(name, "Flota Rebelii");
1658 } else if (!stricmp(name, "Sentry Gun")) {
1659 strcpy(name, "Dzia\xB3o str.");
1661 } else if (!stricmp(name, "Cargo")) {
1662 strcpy(name, "\xA3\x61\x64unek");
1664 } else if (!stricmp(name, "Knossos Device")) {
1665 strcpy(name, "Urz. Knossos");
1667 } else if (!stricmp(name, "Support")) {
1668 strcpy(name, "Wsparcie");
1670 } else if (!stricmp(name, "Unknown")) {
1671 strcpy(name, "Nieznany");
1673 } else if (!stricmp(name, "Instructor")) {
1674 strcpy(name, "Instruktor");
1676 } else if (!stricmp(name, "Jump Node")) {
1677 strcpy(name, "W\xEAze\xB3 skokowy");
1679 } else if (!stricmp(name, "Escort")) {
1680 strcpy(name, "Eskorta");
1682 } else if (!stricmp(name, "Asteroid Field")) {
1683 strcpy(name, "Pole asteroid");
1685 } else if (!stricmp(name, "Enif Station")) {
1686 strcpy(name, "Stacja Enif");
1688 } else if (!stricmp(name, "Rally Point")) {
1689 strcpy(name, "Pkt zborny");
1691 } else if ((pos = strstr(name, "Transport")) != NULL) {
1692 pos += 9; // strlen of "transport"
1693 strcpy(buf, "Transporter");
1697 } else if ((pos = strstr(name, "Jump Node")) != NULL) {
1698 pos += 9; // strlen of "jump node"
1699 strcpy(buf, "W\xEAze\xB3 skokowy");
1703 } else if (!stricmp(name, "Orion under repair")) {
1704 strcpy(name, "Naprawiany Orion");
1708 // ------------------------------------------------------------------
1709 // lcl_translate_ship_name()
1711 // For displaying ship names in german version in the briefing
1712 // since we cant actually just change them outright.
1714 void lcl_translate_ship_name(char *name)
1716 if (!strcmp(name, "GTDR Amazon Advanced")) {
1717 strcpy(name, "GTDR Amazon VII");
1721 // ------------------------------------------------------------------
1722 // lcl_translate_targetbox_name()
1724 // For displaying ship names in german version in the targetbox
1725 // since we cant actually just change them outright.
1727 void lcl_translate_targetbox_name(char *name)
1732 if ((pos = strstr(name, "Sentry")) != NULL) {
1733 pos += 6; // strlen of "sentry"
1734 strcpy(buf, "Sperrgesch\x81tz");
1738 } else if ((pos = strstr(name, "Support")) != NULL) {
1739 pos += 7; // strlen of "support"
1740 strcpy(buf, "Versorger");
1744 } else if ((pos = strstr(name, "Unknown")) != NULL) {
1745 pos += 7; // strlen of "unknown"
1746 strcpy(buf, "Unbekannt");
1750 } else if ((pos = strstr(name, "Drone")) != NULL) {
1751 pos += 5; // strlen of "drone"
1752 strcpy(buf, "Drohne");
1756 } else if ((pos = strstr(name, "Jump Node")) != NULL) {
1757 pos += 9; // strlen of "jump node"
1758 strcpy(buf, "Sprungknoten");
1762 } else if (!stricmp(name, "Instructor")) {
1763 strcpy(name, "Ausbilder");
1765 } else if (!stricmp(name, "NTF Vessel")) {
1766 strcpy(name, "NTF-Schiff");
1768 } else if (!stricmp(name, "Enif Station")) {
1769 strcpy(name, "Station Enif");
1773 // ------------------------------------------------------------------
1774 // lcl_translate_targetbox_name_pl()
1776 // For displaying ship names in polish version in the targetbox
1777 // since we cant actually just change them outright.
1779 void lcl_translate_targetbox_name_pl(char *name)
1784 if ((pos = strstr(name, "Sentry")) != NULL) {
1785 pos += 6; // strlen of "sentry"
1786 strcpy(buf, "Stra\xBFnik");
1790 } else if ((pos = strstr(name, "Support")) != NULL) {
1791 pos += 7; // strlen of "support"
1792 strcpy(buf, "Wsparcie");
1796 } else if ((pos = strstr(name, "Unknown")) != NULL) {
1797 pos += 7; // strlen of "unknown"
1798 strcpy(buf, "Nieznany");
1802 } else if ((pos = strstr(name, "Drone")) != NULL) {
1803 pos += 5; // strlen of "drone"
1804 strcpy(buf, "Sonda");
1808 } else if ((pos = strstr(name, "Jump Node")) != NULL) {
1809 pos += 9; // strlen of "jump node"
1810 strcpy(buf, "W\xEAze\xB3 skokowy");
1814 } else if (!stricmp(name, "Instructor")) {
1815 strcpy(name, "Instruktor");
1817 } else if (!stricmp(name, "NTF Vessel")) {
1818 strcpy(name, "Okr\xEAt NTF");
1820 } else if (!stricmp(name, "Enif Station")) {
1821 strcpy(name, "Stacja Enif");