# newgrf_text.cpp

Go to the documentation of this file.

    00001 /* $Id: newgrf_text.cpp 25500 2013-06-28 19:29:08Z rubidium $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
    00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    00008  */
    00009
    00020 #include "stdafx.h"
    00021 #include "newgrf.h"
    00022 #include "strings_func.h"
    00023 #include "newgrf_storage.h"
    00024 #include "newgrf_text.h"
    00025 #include "string_func.h"
    00026 #include "date_type.h"
    00027 #include "debug.h"
    00028 #include "core/alloc_type.hpp"
    00029 #include "core/smallmap_type.hpp"
    00030 #include "language.h"
    00031
    00032 #include "table/strings.h"
    00033 #include "table/control_codes.h"
    00034
    00035 #define GRFTAB  28
    00036 #define TABSIZE 11
    00037
    00045 StringID TTDPStringIDToOTTDStringIDMapping(StringID str)
    00046 {
    00047   /* StringID table for TextIDs 0x4E->0x6D */
    00048   static const StringID units_volume[] = {
    00049     STR_ITEMS,      STR_PASSENGERS, STR_TONS,       STR_BAGS,
    00050     STR_LITERS,     STR_ITEMS,      STR_CRATES,     STR_TONS,
    00051     STR_TONS,       STR_TONS,       STR_TONS,       STR_BAGS,
    00052     STR_TONS,       STR_TONS,       STR_TONS,       STR_BAGS,
    00053     STR_TONS,       STR_TONS,       STR_BAGS,       STR_LITERS,
    00054     STR_TONS,       STR_LITERS,     STR_TONS,       STR_ITEMS,
    00055     STR_BAGS,       STR_LITERS,     STR_TONS,       STR_ITEMS,
    00056     STR_TONS,       STR_ITEMS,      STR_LITERS,     STR_ITEMS
    00057   };
    00058
    00059   /* A string straight from a NewGRF; no need to remap this as it's already mapped. */
    00060   if (IsInsideMM(str, 0xD000, 0xD7FF)) return str;
    00061
    00062 #define TEXTID_TO_STRINGID(begin, end, stringid, stringend) \
    00063   assert_compile(stringend - stringid == end - begin); \
    00064   if (str >= begin && str <= end) return str + (stringid - begin)
    00065
    00066   /* We have some changes in our cargo strings, resulting in some missing. */
    00067   TEXTID_TO_STRINGID(0x000E, 0x002D, STR_CARGO_PLURAL_NOTHING,                      STR_CARGO_PLURAL_FIZZY_DRINKS);
    00068   TEXTID_TO_STRINGID(0x002E, 0x004D, STR_CARGO_SINGULAR_NOTHING,                    STR_CARGO_SINGULAR_FIZZY_DRINK);
    00069   if (str >= 0x004E && str <= 0x006D) return units_volume[str - 0x004E];
    00070   TEXTID_TO_STRINGID(0x006E, 0x008D, STR_QUANTITY_NOTHING,                          STR_QUANTITY_FIZZY_DRINKS);
    00071   TEXTID_TO_STRINGID(0x008E, 0x00AD, STR_ABBREV_NOTHING,                            STR_ABBREV_FIZZY_DRINKS);
    00072   TEXTID_TO_STRINGID(0x00D1, 0x00E0, STR_COLOUR_DARK_BLUE,                          STR_COLOUR_WHITE);
    00073
    00074   /* Map building names according to our lang file changes. There are several
    00075    * ranges of house ids, all of which need to be remapped to allow newgrfs
    00076    * to use original house names. */
    00077   TEXTID_TO_STRINGID(0x200F, 0x201F, STR_TOWN_BUILDING_NAME_TALL_OFFICE_BLOCK_1,    STR_TOWN_BUILDING_NAME_OLD_HOUSES_1);
    00078   TEXTID_TO_STRINGID(0x2036, 0x2041, STR_TOWN_BUILDING_NAME_COTTAGES_1,             STR_TOWN_BUILDING_NAME_SHOPPING_MALL_1);
    00079   TEXTID_TO_STRINGID(0x2059, 0x205C, STR_TOWN_BUILDING_NAME_IGLOO_1,                STR_TOWN_BUILDING_NAME_PIGGY_BANK_1);
    00080
    00081   /* Same thing for industries */
    00082   TEXTID_TO_STRINGID(0x4802, 0x4826, STR_INDUSTRY_NAME_COAL_MINE,                   STR_INDUSTRY_NAME_SUGAR_MINE);
    00083   TEXTID_TO_STRINGID(0x482D, 0x482E, STR_NEWS_INDUSTRY_CONSTRUCTION,                STR_NEWS_INDUSTRY_PLANTED);
    00084   TEXTID_TO_STRINGID(0x4832, 0x4834, STR_NEWS_INDUSTRY_CLOSURE_GENERAL,             STR_NEWS_INDUSTRY_CLOSURE_LACK_OF_TREES);
    00085   TEXTID_TO_STRINGID(0x4835, 0x4838, STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_GENERAL, STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_FARM);
    00086   TEXTID_TO_STRINGID(0x4839, 0x483A, STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_GENERAL, STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_FARM);
    00087
    00088   switch (str) {
    00089     case 0x4830: return STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY;
    00090     case 0x4831: return STR_ERROR_FOREST_CAN_ONLY_BE_PLANTED;
    00091     case 0x483B: return STR_ERROR_CAN_ONLY_BE_POSITIONED;
    00092   }
    00093 #undef TEXTID_TO_STRINGID
    00094
    00095   if (str == STR_NULL) return STR_EMPTY;
    00096
    00097   DEBUG(grf, 0, "Unknown StringID 0x%04X remapped to STR_EMPTY. Please open a Feature Request if you need it", str);
    00098
    00099   return STR_EMPTY;
    00100 }
    00101
    00107 enum GRFBaseLanguages {
    00108   GRFLB_AMERICAN    = 0x01,
    00109   GRFLB_ENGLISH     = 0x02,
    00110   GRFLB_GERMAN      = 0x04,
    00111   GRFLB_FRENCH      = 0x08,
    00112   GRFLB_SPANISH     = 0x10,
    00113   GRFLB_GENERIC     = 0x80,
    00114 };
    00115
    00116 enum GRFExtendedLanguages {
    00117   GRFLX_AMERICAN    = 0x00,
    00118   GRFLX_ENGLISH     = 0x01,
    00119   GRFLX_GERMAN      = 0x02,
    00120   GRFLX_FRENCH      = 0x03,
    00121   GRFLX_SPANISH     = 0x04,
    00122   GRFLX_UNSPECIFIED = 0x7F,
    00123 };
    00124
    00130 struct GRFText {
    00131 public:
    00142   static GRFText *New(byte langid, const char *text, size_t len)
    00143   {
    00144     return new (len) GRFText(langid, text, len);
    00145   }
    00146
    00152   static GRFText *Copy(GRFText *orig)
    00153   {
    00154     return GRFText::New(orig->langid, orig->text, orig->len);
    00155   }
    00156
    00162   void *operator new(size_t size)
    00163   {
    00164     NOT_REACHED();
    00165   }
    00166
    00171   void operator delete(void *p)
    00172   {
    00173     free(p);
    00174   }
    00175 private:
    00182   GRFText(byte langid_, const char *text_, size_t len_) : next(NULL), len(len_), langid(langid_)
    00183   {
    00184     /* We need to use memcpy instead of strcpy due to
    00185      * the possibility of "choice lists" and therefore
    00186      * intermediate string terminators. */
    00187     memcpy(this->text, text_, len);
    00188   }
    00189
    00196   void *operator new(size_t size, size_t extra)
    00197   {
    00198     return MallocT<byte>(size + extra);
    00199   }
    00200
    00201 public:
    00202   GRFText *next;
    00203   size_t len;
    00204   byte langid;
    00205   char text[];
    00206 };
    00207
    00208
    00214 struct GRFTextEntry {
    00215   uint32 grfid;
    00216   uint16 stringid;
    00217   StringID def_string;
    00218   GRFText *textholder;
    00219 };
    00220
    00221
    00222 static uint _num_grf_texts = 0;
    00223 static GRFTextEntry _grf_text[(1 << TABSIZE) * 3];
    00224 static byte _currentLangID = GRFLX_ENGLISH;
    00225
    00232 int LanguageMap::GetMapping(int newgrf_id, bool gender) const
    00233 {
    00234   const SmallVector<Mapping, 1> &map = gender ? this->gender_map : this->case_map;
    00235   for (const Mapping *m = map.Begin(); m != map.End(); m++) {
    00236     if (m->newgrf_id == newgrf_id) return m->openttd_id;
    00237   }
    00238   return -1;
    00239 }
    00240
    00247 int LanguageMap::GetReverseMapping(int openttd_id, bool gender) const
    00248 {
    00249   const SmallVector<Mapping, 1> &map = gender ? this->gender_map : this->case_map;
    00250   for (const Mapping *m = map.Begin(); m != map.End(); m++) {
    00251     if (m->openttd_id == openttd_id) return m->newgrf_id;
    00252   }
    00253   return -1;
    00254 }
    00255
    00257 struct UnmappedChoiceList : ZeroedMemoryAllocator {
    00259   ~UnmappedChoiceList()
    00260   {
    00261     for (SmallPair<byte, char *> *p = this->strings.Begin(); p < this->strings.End(); p++) {
    00262       free(p->second);
    00263     }
    00264   }
    00265
    00272   UnmappedChoiceList(StringControlCode type, char *old_d, int offset) :
    00273     type(type), old_d(old_d), offset(offset)
    00274   {
    00275   }
    00276
    00277   StringControlCode type;
    00278   char *old_d;
    00279   int offset;
    00280
    00282   SmallMap<byte, char *> strings;
    00283
    00289   char *Flush(const LanguageMap *lm)
    00290   {
    00291     if (!this->strings.Contains(0)) {
    00292       /* In case of a (broken) NewGRF without a default,
    00293        * assume an empty string. */
    00294       grfmsg(1, "choice list misses default value");
    00295       this->strings[0] = strdup("");
    00296     }
    00297
    00298     char *d = old_d;
    00299     if (lm == NULL && this->type != SCC_PLURAL_LIST) {
    00300       /* In case there is no mapping, just ignore everything but the default.
    00301        * A probable cause for this happening is when the language file has
    00302        * been removed by the user and as such no mapping could be made. */
    00303       size_t len = strlen(this->strings[0]);
    00304       memcpy(d, this->strings[0], len);
    00305       return d + len;
    00306     }
    00307
    00308     d += Utf8Encode(d, this->type);
    00309
    00310     if (this->type == SCC_SWITCH_CASE) {
    00311       /*
    00312        * Format for case switch:
    00313        * <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
    00314        * Each LEN is printed using 2 bytes in big endian order.
    00315        */
    00316
    00317       /* "<NUM CASES>" */
    00318       int count = 0;
    00319       for (uint8 i = 0; i < _current_language->num_cases; i++) {
    00320         /* Count the ones we have a mapped string for. */
    00321         if (this->strings.Contains(lm->GetReverseMapping(i, false))) count++;
    00322       }
    00323       *d++ = count;
    00324
    00325       for (uint8 i = 0; i < _current_language->num_cases; i++) {
    00326         /* Resolve the string we're looking for. */
    00327         int idx = lm->GetReverseMapping(i, false);
    00328         if (!this->strings.Contains(idx)) continue;
    00329         char *str = this->strings[idx];
    00330
    00331         /* "<CASEn>" */
    00332         *d++ = i + 1;
    00333
    00334         /* "<LENn>" */
    00335         size_t len = strlen(str) + 1;
    00336         *d++ = GB(len, 8, 8);
    00337         *d++ = GB(len, 0, 8);
    00338
    00339         /* "<STRINGn>" */
    00340         memcpy(d, str, len);
    00341         d += len;
    00342       }
    00343
    00344       /* "<STRINGDEFAULT>" */
    00345       size_t len = strlen(this->strings[0]) + 1;
    00346       memcpy(d, this->strings[0], len);
    00347       d += len;
    00348     } else {
    00349       if (this->type == SCC_PLURAL_LIST) {
    00350         *d++ = lm->plural_form;
    00351       }
    00352
    00353       /*
    00354        * Format for choice list:
    00355        * <OFFSET> <NUM CHOICES> <LENs> <STRINGs>
    00356        */
    00357
    00358       /* "<OFFSET>" */
    00359       *d++ = this->offset - 0x80;
    00360
    00361       /* "<NUM CHOICES>" */
    00362       int count = (this->type == SCC_GENDER_LIST ? _current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
    00363       *d++ = count;
    00364
    00365       /* "<LENs>" */
    00366       for (int i = 0; i < count; i++) {
    00367         int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
    00368         const char *str = this->strings[this->strings.Contains(idx) ? idx : 0];
    00369         size_t len = strlen(str) + 1;
    00370         if (len > 0xFF) grfmsg(1, "choice list string is too long");
    00371         *d++ = GB(len, 0, 8);
    00372       }
    00373
    00374       /* "<STRINGs>" */
    00375       for (int i = 0; i < count; i++) {
    00376         int idx = (this->type == SCC_GENDER_LIST ? lm->GetReverseMapping(i, true) : i + 1);
    00377         const char *str = this->strings[this->strings.Contains(idx) ? idx : 0];
    00378         /* Limit the length of the string we copy to 0xFE. The length is written above
    00379          * as a byte and we need room for the final '\0'. */
    00380         size_t len = min<size_t>(0xFE, strlen(str));
    00381         memcpy(d, str, len);
    00382         d += len;
    00383         *d++ = '\0';
    00384       }
    00385     }
    00386     return d;
    00387   }
    00388 };
    00389
    00400 char *TranslateTTDPatchCodes(uint32 grfid, uint8 language_id, bool allow_newlines, const char *str, int *olen, StringControlCode byte80)
    00401 {
    00402   char *tmp = MallocT<char>(strlen(str) * 10 + 1); // Allocate space to allow for expansion
    00403   char *d = tmp;
    00404   bool unicode = false;
    00405   WChar c;
    00406   size_t len = Utf8Decode(&c, str);
    00407
    00408   /* Helper variable for a possible (string) mapping. */
    00409   UnmappedChoiceList *mapping = NULL;
    00410
    00411   if (c == NFO_UTF8_IDENTIFIER) {
    00412     unicode = true;
    00413     str += len;
    00414   }
    00415
    00416   for (;;) {
    00417     if (unicode && Utf8EncodedCharLen(*str) != 0) {
    00418       c = Utf8Consume(&str);
    00419       /* 'Magic' range of control codes. */
    00420       if (GB(c, 8, 8) == 0xE0) {
    00421         c = GB(c, 0, 8);
    00422       } else if (c >= 0x20) {
    00423         if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
    00424         d += Utf8Encode(d, c);
    00425         continue;
    00426       }
    00427     } else {
    00428       c = (byte)*str++;
    00429     }
    00430     if (c == '\0') break;
    00431
    00432     switch (c) {
    00433       case 0x01:
    00434         if (str[0] == '\0') goto string_end;
    00435         d += Utf8Encode(d, ' ');
    00436         str++;
    00437         break;
    00438       case 0x0A: break;
    00439       case 0x0D:
    00440         if (allow_newlines) {
    00441           *d++ = 0x0A;
    00442         } else {
    00443           grfmsg(1, "Detected newline in string that does not allow one");
    00444         }
    00445         break;
    00446       case 0x0E: d += Utf8Encode(d, SCC_TINYFONT); break;
    00447       case 0x0F: d += Utf8Encode(d, SCC_BIGFONT); break;
    00448       case 0x1F:
    00449         if (str[0] == '\0' || str[1] == '\0') goto string_end;
    00450         d += Utf8Encode(d, ' ');
    00451         str += 2;
    00452         break;
    00453       case 0x7B:
    00454       case 0x7C:
    00455       case 0x7D:
    00456       case 0x7E:
    00457       case 0x7F: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break;
    00458       case 0x80: d += Utf8Encode(d, byte80); break;
    00459       case 0x81: {
    00460         if (str[0] == '\0' || str[1] == '\0') goto string_end;
    00461         StringID string;
    00462         string  = ((uint8)*str++);
    00463         string |= ((uint8)*str++) << 8;
    00464         d += Utf8Encode(d, SCC_NEWGRF_STRINL);
    00465         d += Utf8Encode(d, MapGRFStringID(grfid, string));
    00466         break;
    00467       }
    00468       case 0x82:
    00469       case 0x83:
    00470       case 0x84: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_DATE_LONG + c - 0x82); break;
    00471       case 0x85: d += Utf8Encode(d, SCC_NEWGRF_DISCARD_WORD);       break;
    00472       case 0x86: d += Utf8Encode(d, SCC_NEWGRF_ROTATE_TOP_4_WORDS); break;
    00473       case 0x87: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_VOLUME_LONG);  break;
    00474       case 0x88: d += Utf8Encode(d, SCC_BLUE);    break;
    00475       case 0x89: d += Utf8Encode(d, SCC_SILVER);  break;
    00476       case 0x8A: d += Utf8Encode(d, SCC_GOLD);    break;
    00477       case 0x8B: d += Utf8Encode(d, SCC_RED);     break;
    00478       case 0x8C: d += Utf8Encode(d, SCC_PURPLE);  break;
    00479       case 0x8D: d += Utf8Encode(d, SCC_LTBROWN); break;
    00480       case 0x8E: d += Utf8Encode(d, SCC_ORANGE);  break;
    00481       case 0x8F: d += Utf8Encode(d, SCC_GREEN);   break;
    00482       case 0x90: d += Utf8Encode(d, SCC_YELLOW);  break;
    00483       case 0x91: d += Utf8Encode(d, SCC_DKGREEN); break;
    00484       case 0x92: d += Utf8Encode(d, SCC_CREAM);   break;
    00485       case 0x93: d += Utf8Encode(d, SCC_BROWN);   break;
    00486       case 0x94: d += Utf8Encode(d, SCC_WHITE);   break;
    00487       case 0x95: d += Utf8Encode(d, SCC_LTBLUE);  break;
    00488       case 0x96: d += Utf8Encode(d, SCC_GRAY);    break;
    00489       case 0x97: d += Utf8Encode(d, SCC_DKBLUE);  break;
    00490       case 0x98: d += Utf8Encode(d, SCC_BLACK);   break;
    00491       case 0x9A: {
    00492         int code = *str++;
    00493         switch (code) {
    00494           case 0x00: goto string_end;
    00495           case 0x01: d += Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_CURRENCY); break;
    00496           /* 0x02: ignore next colour byte is not supported. It works on the final
    00497            * string and as such hooks into the string drawing routine. At that
    00498            * point many things already happened, such as splitting up of strings
    00499            * when drawn over multiple lines or right-to-left translations, which
    00500            * make the behaviour peculiar, e.g. only happening at specific width
    00501            * of windows. Or we need to add another pass over the string to just
    00502            * support this. As such it is not implemented in OpenTTD. */
    00503           case 0x03: {
    00504             if (str[0] == '\0' || str[1] == '\0') goto string_end;
    00505             uint16 tmp  = ((uint8)*str++);
    00506             tmp        |= ((uint8)*str++) << 8;
    00507             d += Utf8Encode(d, SCC_NEWGRF_PUSH_WORD);
    00508             d += Utf8Encode(d, tmp);
    00509             break;
    00510           }
    00511           case 0x04:
    00512             if (str[0] == '\0') goto string_end;
    00513             d += Utf8Encode(d, SCC_NEWGRF_UNPRINT);
    00514             d += Utf8Encode(d, *str++);
    00515             break;
    00516           case 0x06: d += Utf8Encode(d, SCC_NEWGRF_PRINT_BYTE_HEX);          break;
    00517           case 0x07: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_HEX);          break;
    00518           case 0x08: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_HEX);         break;
    00519           /* 0x09, 0x0A are TTDPatch internal use only string codes. */
    00520           case 0x0B: d += Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_HEX);         break;
    00521           case 0x0C: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_STATION_NAME); break;
    00522           case 0x0D: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG);  break;
    00523           case 0x0E:
    00524           case 0x0F: {
    00525             if (str[0] == '\0') goto string_end;
    00526             const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id);
    00527             int index = *str++;
    00528             int mapped = lm != NULL ? lm->GetMapping(index, code == 0x0E) : -1;
    00529             if (mapped >= 0) {
    00530               d += Utf8Encode(d, code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
    00531               d += Utf8Encode(d, code == 0x0E ? mapped : mapped + 1);
    00532             }
    00533             break;
    00534           }
    00535
    00536           case 0x10:
    00537           case 0x11:
    00538             if (str[0] == '\0') goto string_end;
    00539             if (mapping == NULL) {
    00540               if (code == 0x10) str++; // Skip the index
    00541               grfmsg(1, "choice list %s marker found when not expected", code == 0x10 ? "next" : "default");
    00542               break;
    00543             } else {
    00544               /* Terminate the previous string. */
    00545               *d = '\0';
    00546               int index = (code == 0x10 ? *str++ : 0);
    00547               if (mapping->strings.Contains(index)) {
    00548                 grfmsg(1, "duplicate choice list string, ignoring");
    00549                 d++;
    00550               } else {
    00551                 d = mapping->strings[index] = MallocT<char>(strlen(str) * 10 + 1);
    00552               }
    00553             }
    00554             break;
    00555
    00556           case 0x12:
    00557             if (mapping == NULL) {
    00558               grfmsg(1, "choice list end marker found when not expected");
    00559             } else {
    00560               /* Terminate the previous string. */
    00561               *d = '\0';
    00562
    00563               /* Now we can start flushing everything and clean everything up. */
    00564               d = mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id));
    00565               delete mapping;
    00566               mapping = NULL;
    00567             }
    00568             break;
    00569
    00570           case 0x13:
    00571           case 0x14:
    00572           case 0x15:
    00573             if (str[0] == '\0') goto string_end;
    00574             if (mapping != NULL) {
    00575               grfmsg(1, "choice lists can't be stacked, it's going to get messy now...");
    00576               if (code != 0x14) str++;
    00577             } else {
    00578               static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
    00579               mapping = new UnmappedChoiceList(mp[code - 0x13], d, code == 0x14 ? 0 : *str++);
    00580             }
    00581             break;
    00582
    00583           case 0x16:
    00584           case 0x17:
    00585           case 0x18:
    00586           case 0x19:
    00587           case 0x1A: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_DATE_LONG + code - 0x16); break;
    00588
    00589           default:
    00590             grfmsg(1, "missing handler for extended format code");
    00591             break;
    00592         }
    00593         break;
    00594       }
    00595
    00596       case 0x9E: d += Utf8Encode(d, 0x20AC);               break; // Euro
    00597       case 0x9F: d += Utf8Encode(d, 0x0178);               break; // Y with diaeresis
    00598       case 0xA0: d += Utf8Encode(d, SCC_UP_ARROW);         break;
    00599       case 0xAA: d += Utf8Encode(d, SCC_DOWN_ARROW);       break;
    00600       case 0xAC: d += Utf8Encode(d, SCC_CHECKMARK);        break;
    00601       case 0xAD: d += Utf8Encode(d, SCC_CROSS);            break;
    00602       case 0xAF: d += Utf8Encode(d, SCC_RIGHT_ARROW);      break;
    00603       case 0xB4: d += Utf8Encode(d, SCC_TRAIN);            break;
    00604       case 0xB5: d += Utf8Encode(d, SCC_LORRY);            break;
    00605       case 0xB6: d += Utf8Encode(d, SCC_BUS);              break;
    00606       case 0xB7: d += Utf8Encode(d, SCC_PLANE);            break;
    00607       case 0xB8: d += Utf8Encode(d, SCC_SHIP);             break;
    00608       case 0xB9: d += Utf8Encode(d, SCC_SUPERSCRIPT_M1);   break;
    00609       case 0xBC: d += Utf8Encode(d, SCC_SMALL_UP_ARROW);   break;
    00610       case 0xBD: d += Utf8Encode(d, SCC_SMALL_DOWN_ARROW); break;
    00611       default:
    00612         /* Validate any unhandled character */
    00613         if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?';
    00614         d += Utf8Encode(d, c);
    00615         break;
    00616     }
    00617   }
    00618
    00619 string_end:
    00620   if (mapping != NULL) {
    00621     grfmsg(1, "choice list was incomplete, the whole list is ignored");
    00622     delete mapping;
    00623   }
    00624
    00625   *d = '\0';
    00626   if (olen != NULL) *olen = d - tmp + 1;
    00627   tmp = ReallocT(tmp, d - tmp + 1);
    00628   return tmp;
    00629 }
    00630
    00636 void AddGRFTextToList(GRFText **list, GRFText *text_to_add)
    00637 {
    00638   GRFText **ptext, *text;
    00639
    00640   /* Loop through all languages and see if we can replace a string */
    00641   for (ptext = list; (text = *ptext) != NULL; ptext = &text->next) {
    00642     if (text->langid == text_to_add->langid) {
    00643       text_to_add->next = text->next;
    00644       *ptext = text_to_add;
    00645       delete text;
    00646       return;
    00647     }
    00648   }
    00649
    00650   /* If a string wasn't replaced, then we must append the new string */
    00651   *ptext = text_to_add;
    00652 }
    00653
    00663 void AddGRFTextToList(struct GRFText **list, byte langid, uint32 grfid, bool allow_newlines, const char *text_to_add)
    00664 {
    00665   int len;
    00666   char *translatedtext = TranslateTTDPatchCodes(grfid, langid, allow_newlines, text_to_add, &len);
    00667   GRFText *newtext = GRFText::New(langid, translatedtext, len);
    00668   free(translatedtext);
    00669
    00670   AddGRFTextToList(list, newtext);
    00671 }
    00672
    00679 void AddGRFTextToList(struct GRFText **list, const char *text_to_add)
    00680 {
    00681   AddGRFTextToList(list, GRFText::New(0x7F, text_to_add, strlen(text_to_add) + 1));
    00682 }
    00683
    00689 GRFText *DuplicateGRFText(GRFText *orig)
    00690 {
    00691   GRFText *newtext = NULL;
    00692   GRFText **ptext = &newtext;
    00693   for (; orig != NULL; orig = orig->next) {
    00694     *ptext = GRFText::Copy(orig);
    00695     ptext = &(*ptext)->next;
    00696   }
    00697   return newtext;
    00698 }
    00699
    00703 StringID AddGRFString(uint32 grfid, uint16 stringid, byte langid_to_add, bool new_scheme, bool allow_newlines, const char *text_to_add, StringID def_string)
    00704 {
    00705   char *translatedtext;
    00706   uint id;
    00707
    00708   /* When working with the old language scheme (grf_version is less than 7) and
    00709    * English or American is among the set bits, simply add it as English in
    00710    * the new scheme, i.e. as langid = 1.
    00711    * If English is set, it is pretty safe to assume the translations are not
    00712    * actually translated.
    00713    */
    00714   if (!new_scheme) {
    00715     if (langid_to_add & (GRFLB_AMERICAN | GRFLB_ENGLISH)) {
    00716       langid_to_add = GRFLX_ENGLISH;
    00717     } else {
    00718       StringID ret = STR_EMPTY;
    00719       if (langid_to_add & GRFLB_GERMAN)  ret = AddGRFString(grfid, stringid, GRFLX_GERMAN,  true, allow_newlines, text_to_add, def_string);
    00720       if (langid_to_add & GRFLB_FRENCH)  ret = AddGRFString(grfid, stringid, GRFLX_FRENCH,  true, allow_newlines, text_to_add, def_string);
    00721       if (langid_to_add & GRFLB_SPANISH) ret = AddGRFString(grfid, stringid, GRFLX_SPANISH, true, allow_newlines, text_to_add, def_string);
    00722       return ret;
    00723     }
    00724   }
    00725
    00726   for (id = 0; id < _num_grf_texts; id++) {
    00727     if (_grf_text[id].grfid == grfid && _grf_text[id].stringid == stringid) {
    00728       break;
    00729     }
    00730   }
    00731
    00732   /* Too many strings allocated, return empty */
    00733   if (id == lengthof(_grf_text)) return STR_EMPTY;
    00734
    00735   int len;
    00736   translatedtext = TranslateTTDPatchCodes(grfid, langid_to_add, allow_newlines, text_to_add, &len);
    00737
    00738   GRFText *newtext = GRFText::New(langid_to_add, translatedtext, len);
    00739
    00740   free(translatedtext);
    00741
    00742   /* If we didn't find our stringid and grfid in the list, allocate a new id */
    00743   if (id == _num_grf_texts) _num_grf_texts++;
    00744
    00745   if (_grf_text[id].textholder == NULL) {
    00746     _grf_text[id].grfid      = grfid;
    00747     _grf_text[id].stringid   = stringid;
    00748     _grf_text[id].def_string = def_string;
    00749   }
    00750   AddGRFTextToList(&_grf_text[id].textholder, newtext);
    00751
    00752   grfmsg(3, "Added 0x%X: grfid %08X string 0x%X lang 0x%X string '%s'", id, grfid, stringid, newtext->langid, newtext->text);
    00753
    00754   return (GRFTAB << TABSIZE) + id;
    00755 }
    00756
    00757 /* Used to remember the grfid that the last retrieved string came from */
    00758 static uint32 _last_grfid = 0;
    00759
    00763 StringID GetGRFStringID(uint32 grfid, uint16 stringid)
    00764 {
    00765   uint id;
    00766
    00767   /* grfid is zero when we're being called via an include */
    00768   if (grfid == 0) grfid = _last_grfid;
    00769
    00770   for (id = 0; id < _num_grf_texts; id++) {
    00771     if (_grf_text[id].grfid == grfid && _grf_text[id].stringid == stringid) {
    00772       return (GRFTAB << TABSIZE) + id;
    00773     }
    00774   }
    00775
    00776   return STR_UNDEFINED;
    00777 }
    00778
    00779
    00787 const char *GetGRFStringFromGRFText(const GRFText *text)
    00788 {
    00789   const char *default_text = NULL;
    00790
    00791   /* Search the list of lang-strings of this stringid for current lang */
    00792   for (; text != NULL; text = text->next) {
    00793     if (text->langid == _currentLangID) return text->text;
    00794
    00795     /* If the current string is English or American, set it as the
    00796      * fallback language if the specific language isn't available. */
    00797     if (text->langid == GRFLX_UNSPECIFIED || (default_text == NULL && (text->langid == GRFLX_ENGLISH || text->langid == GRFLX_AMERICAN))) {
    00798       default_text = text->text;
    00799     }
    00800   }
    00801
    00802   return default_text;
    00803 }
    00804
    00808 const char *GetGRFStringPtr(uint16 stringid)
    00809 {
    00810   assert(_grf_text[stringid].grfid != 0);
    00811
    00812   /* Remember this grfid in case the string has included text */
    00813   _last_grfid = _grf_text[stringid].grfid;
    00814
    00815   const char *str = GetGRFStringFromGRFText(_grf_text[stringid].textholder);
    00816   if (str != NULL) return str;
    00817
    00818   /* Use the default string ID if the fallback string isn't available */
    00819   return GetStringPtr(_grf_text[stringid].def_string);
    00820 }
    00821
    00830 void SetCurrentGrfLangID(byte language_id)
    00831 {
    00832   _currentLangID = language_id;
    00833 }
    00834
    00835 bool CheckGrfLangID(byte lang_id, byte grf_version)
    00836 {
    00837   if (grf_version < 7) {
    00838     switch (_currentLangID) {
    00839       case GRFLX_GERMAN:  return (lang_id & GRFLB_GERMAN)  != 0;
    00840       case GRFLX_FRENCH:  return (lang_id & GRFLB_FRENCH)  != 0;
    00841       case GRFLX_SPANISH: return (lang_id & GRFLB_SPANISH) != 0;
    00842       default:            return (lang_id & (GRFLB_ENGLISH | GRFLB_AMERICAN)) != 0;
    00843     }
    00844   }
    00845
    00846   return (lang_id == _currentLangID || lang_id == GRFLX_UNSPECIFIED);
    00847 }
    00848
    00853 void CleanUpGRFText(GRFText *grftext)
    00854 {
    00855   while (grftext != NULL) {
    00856     GRFText *grftext2 = grftext->next;
    00857     delete grftext;
    00858     grftext = grftext2;
    00859   }
    00860 }
    00861
    00866 void CleanUpStrings()
    00867 {
    00868   uint id;
    00869
    00870   for (id = 0; id < _num_grf_texts; id++) {
    00871     CleanUpGRFText(_grf_text[id].textholder);
    00872     _grf_text[id].grfid      = 0;
    00873     _grf_text[id].stringid   = 0;
    00874     _grf_text[id].textholder = NULL;
    00875   }
    00876
    00877   _num_grf_texts = 0;
    00878 }
    00879
    00880 struct TextRefStack {
    00881   byte stack[0x30];
    00882   byte position;
    00883   bool used;
    00884
    00885   TextRefStack() : used(false) {}
    00886
    00887   TextRefStack(const TextRefStack &stack) :
    00888     position(stack.position),
    00889     used(stack.used)
    00890   {
    00891     memcpy(this->stack, stack.stack, sizeof(this->stack));
    00892   }
    00893
    00894   uint8  PopUnsignedByte()  { assert(this->position < lengthof(this->stack)); return this->stack[this->position++]; }
    00895   int8   PopSignedByte()    { return (int8)this->PopUnsignedByte(); }
    00896
    00897   uint16 PopUnsignedWord()
    00898   {
    00899     uint16 val = this->PopUnsignedByte();
    00900     return val | (this->PopUnsignedByte() << 8);
    00901   }
    00902   int16  PopSignedWord()    { return (int32)this->PopUnsignedWord(); }
    00903
    00904   uint32 PopUnsignedDWord()
    00905   {
    00906     uint32 val = this->PopUnsignedWord();
    00907     return val | (this->PopUnsignedWord() << 16);
    00908   }
    00909   int32  PopSignedDWord()   { return (int32)this->PopUnsignedDWord(); }
    00910
    00911   uint64 PopUnsignedQWord()
    00912   {
    00913     uint64 val = this->PopUnsignedDWord();
    00914     return val | (((uint64)this->PopUnsignedDWord()) << 32);
    00915   }
    00916   int64  PopSignedQWord()   { return (int64)this->PopUnsignedQWord(); }
    00917
    00919   void RotateTop4Words()
    00920   {
    00921     byte tmp[2];
    00922     for (int i = 0; i  < 2; i++) tmp[i] = this->stack[this->position + i + 6];
    00923     for (int i = 5; i >= 0; i--) this->stack[this->position + i + 2] = this->stack[this->position + i];
    00924     for (int i = 0; i  < 2; i++) this->stack[this->position + i] = tmp[i];
    00925   }
    00926
    00927   void PushWord(uint16 word)
    00928   {
    00929     if (this->position >= 2) {
    00930       this->position -= 2;
    00931     } else {
    00932       for (int i = lengthof(stack) - 1; i >= this->position + 2; i--) {
    00933         this->stack[i] = this->stack[i - 2];
    00934       }
    00935     }
    00936     this->stack[this->position]     = GB(word, 0, 8);
    00937     this->stack[this->position + 1] = GB(word, 8, 8);
    00938   }
    00939
    00940   void ResetStack()  { this->position = 0; this->used = true; }
    00941   void RewindStack() { this->position = 0; }
    00942 };
    00943
    00945 static TextRefStack _newgrf_textrefstack;
    00946
    00951 bool UsingNewGRFTextStack()
    00952 {
    00953   return _newgrf_textrefstack.used;
    00954 }
    00955
    00960 struct TextRefStack *CreateTextRefStackBackup()
    00961 {
    00962   return new TextRefStack(_newgrf_textrefstack);
    00963 }
    00964
    00969 void RestoreTextRefStackBackup(struct TextRefStack *backup)
    00970 {
    00971   _newgrf_textrefstack = *backup;
    00972   delete backup;
    00973 }
    00974
    00992 void StartTextRefStackUsage(byte numEntries, const uint32 *values)
    00993 {
    00994   extern TemporaryStorageArray<int32, 0x110> _temp_store;
    00995
    00996   _newgrf_textrefstack.ResetStack();
    00997
    00998   byte *p = _newgrf_textrefstack.stack;
    00999   for (uint i = 0; i < numEntries; i++) {
    01000     uint32 value = values != NULL ? values[i] : _temp_store.GetValue(0x100 + i);
    01001     for (uint j = 0; j < 32; j += 8) {
    01002       *p = GB(value, j, 8);
    01003       p++;
    01004     }
    01005   }
    01006 }
    01007
    01009 void StopTextRefStackUsage()
    01010 {
    01011   _newgrf_textrefstack.used = false;
    01012 }
    01013
    01014 void RewindTextRefStack()
    01015 {
    01016   _newgrf_textrefstack.RewindStack();
    01017 }
    01018
    01028 uint RemapNewGRFStringControlCode(uint scc, char *buf_start, char **buff, const char **str, int64 *argv, bool modify_argv)
    01029 {
    01030   if (_newgrf_textrefstack.used && modify_argv) {
    01031     switch (scc) {
    01032       default: NOT_REACHED();
    01033       case SCC_NEWGRF_PRINT_BYTE_SIGNED:      *argv = _newgrf_textrefstack.PopSignedByte();    break;
    01034       case SCC_NEWGRF_PRINT_QWORD_CURRENCY:   *argv = _newgrf_textrefstack.PopSignedQWord();   break;
    01035
    01036       case SCC_NEWGRF_PRINT_DWORD_CURRENCY:
    01037       case SCC_NEWGRF_PRINT_DWORD_SIGNED:     *argv = _newgrf_textrefstack.PopSignedDWord();   break;
    01038
    01039       case SCC_NEWGRF_PRINT_BYTE_HEX:         *argv = _newgrf_textrefstack.PopUnsignedByte();  break;
    01040       case SCC_NEWGRF_PRINT_QWORD_HEX:        *argv = _newgrf_textrefstack.PopUnsignedQWord(); break;
    01041
    01042       case SCC_NEWGRF_PRINT_WORD_SPEED:
    01043       case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG:
    01044       case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT:
    01045       case SCC_NEWGRF_PRINT_WORD_SIGNED:      *argv = _newgrf_textrefstack.PopSignedWord();    break;
    01046
    01047       case SCC_NEWGRF_PRINT_WORD_HEX:
    01048       case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG:
    01049       case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT:
    01050       case SCC_NEWGRF_PRINT_WORD_POWER:
    01051       case SCC_NEWGRF_PRINT_WORD_STATION_NAME:
    01052       case SCC_NEWGRF_PRINT_WORD_UNSIGNED:    *argv = _newgrf_textrefstack.PopUnsignedWord();  break;
    01053
    01054       case SCC_NEWGRF_PRINT_DWORD_DATE_LONG:
    01055       case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT:
    01056       case SCC_NEWGRF_PRINT_DWORD_HEX:        *argv = _newgrf_textrefstack.PopUnsignedDWord(); break;
    01057
    01058       case SCC_NEWGRF_PRINT_WORD_DATE_LONG:
    01059       case SCC_NEWGRF_PRINT_WORD_DATE_SHORT:  *argv = _newgrf_textrefstack.PopUnsignedWord() + DAYS_TILL_ORIGINAL_BASE_YEAR; break;
    01060
    01061       case SCC_NEWGRF_DISCARD_WORD:           _newgrf_textrefstack.PopUnsignedWord(); break;
    01062
    01063       case SCC_NEWGRF_ROTATE_TOP_4_WORDS:     _newgrf_textrefstack.RotateTop4Words(); break;
    01064       case SCC_NEWGRF_PUSH_WORD:              _newgrf_textrefstack.PushWord(Utf8Consume(str)); break;
    01065       case SCC_NEWGRF_UNPRINT:                *buff = max(*buff - Utf8Consume(str), buf_start); break;
    01066
    01067       case SCC_NEWGRF_PRINT_WORD_STRING_ID:
    01068         *argv = TTDPStringIDToOTTDStringIDMapping(_newgrf_textrefstack.PopUnsignedWord());
    01069         break;
    01070     }
    01071   }
    01072
    01073   switch (scc) {
    01074     default: NOT_REACHED();
    01075     case SCC_NEWGRF_PRINT_DWORD_SIGNED:
    01076     case SCC_NEWGRF_PRINT_WORD_SIGNED:
    01077     case SCC_NEWGRF_PRINT_BYTE_SIGNED:
    01078     case SCC_NEWGRF_PRINT_WORD_UNSIGNED:
    01079       return SCC_COMMA;
    01080
    01081     case SCC_NEWGRF_PRINT_BYTE_HEX:
    01082     case SCC_NEWGRF_PRINT_WORD_HEX:
    01083     case SCC_NEWGRF_PRINT_DWORD_HEX:
    01084     case SCC_NEWGRF_PRINT_QWORD_HEX:
    01085       return SCC_HEX;
    01086
    01087     case SCC_NEWGRF_PRINT_DWORD_CURRENCY:
    01088     case SCC_NEWGRF_PRINT_QWORD_CURRENCY:
    01089       return SCC_CURRENCY_LONG;
    01090
    01091     case SCC_NEWGRF_PRINT_WORD_STRING_ID:
    01092       return SCC_NEWGRF_PRINT_WORD_STRING_ID;
    01093
    01094     case SCC_NEWGRF_PRINT_WORD_DATE_LONG:
    01095     case SCC_NEWGRF_PRINT_DWORD_DATE_LONG:
    01096       return SCC_DATE_LONG;
    01097
    01098     case SCC_NEWGRF_PRINT_WORD_DATE_SHORT:
    01099     case SCC_NEWGRF_PRINT_DWORD_DATE_SHORT:
    01100       return SCC_DATE_SHORT;
    01101
    01102     case SCC_NEWGRF_PRINT_WORD_SPEED:
    01103       return SCC_VELOCITY;
    01104
    01105     case SCC_NEWGRF_PRINT_WORD_VOLUME_LONG:
    01106       return SCC_VOLUME_LONG;
    01107
    01108     case SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT:
    01109       return SCC_VOLUME_SHORT;
    01110
    01111     case SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG:
    01112       return SCC_WEIGHT_LONG;
    01113
    01114     case SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT:
    01115       return SCC_WEIGHT_SHORT;
    01116
    01117     case SCC_NEWGRF_PRINT_WORD_POWER:
    01118       return SCC_POWER;
    01119
    01120     case SCC_NEWGRF_PRINT_WORD_STATION_NAME:
    01121       return SCC_STATION_NAME;
    01122
    01123     case SCC_NEWGRF_DISCARD_WORD:
    01124     case SCC_NEWGRF_ROTATE_TOP_4_WORDS:
    01125     case SCC_NEWGRF_PUSH_WORD:
    01126     case SCC_NEWGRF_UNPRINT:
    01127       return 0;
    01128   }
    01129 }