tron@2186: /* $Id$ */ tron@2186: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@2291: #include "currency.h" tron@2163: #include "functions.h" tron@1317: #include "string.h" tron@1309: #include "strings.h" tron@507: #include "table/strings.h" tron@1306: #include "namegen.h" truelight@0: #include "station.h" truelight@0: #include "town.h" truelight@0: #include "vehicle.h" darkvater@6: #include "news.h" tron@430: #include "screenshot.h" truelight@1542: #include "waypoint.h" ludde@2070: #include "industry.h" tron@2159: #include "variables.h" belugas@3601: #include "newgrf_text.h" belugas@3616: #include "table/landscape_const.h" belugas@4120: #include "music.h" rubidium@4261: #include "date.h" truelight@0: Darkvater@4219: #ifdef WIN32 Darkvater@4219: /* for opendir/readdir/closedir */ Darkvater@4219: # include "fios.h" Darkvater@4219: #else Darkvater@4219: # include Darkvater@4219: # include Darkvater@4219: #endif /* WIN32 */ Darkvater@4219: tron@2201: char _userstring[128]; tron@2201: ludde@2063: static char *StationGetSpecialString(char *buff, int x); ludde@2063: static char *GetSpecialTownNameString(char *buff, int ind, uint32 seed); ludde@2063: static char *GetSpecialPlayerNameString(char *buff, int ind, const int32 *argv); truelight@0: ludde@2087: static char *FormatString(char *buff, const char *str, const int32 *argv, uint casei); truelight@0: darkvater@663: extern const char _openttd_revision[]; darkvater@659: tron@1319: typedef struct LanguagePack { truelight@0: uint32 ident; rubidium@4434: uint32 version; // 32-bits of auto generated version info which is basically a hash of strings.h rubidium@4434: char name[32]; // the international name of this language rubidium@4434: char own_name[32]; // the localized name of this language rubidium@4434: char isocode[16]; // the ISO code for the language (not country code) rubidium@4434: uint16 offsets[32]; // the offsets rubidium@4434: byte plural_form; // how to compute plural forms rubidium@4434: byte pad[3]; // pad header to be a multiple of 4 tron@1319: char data[VARARRAY_SIZE]; tron@1319: } LanguagePack; tron@1319: tron@1319: static char **_langpack_offs; tron@1319: static LanguagePack *_langpack; tron@1319: static uint _langtab_num[32]; // Offset into langpack offs tron@1319: static uint _langtab_start[32]; // Offset into langpack offs truelight@0: tron@1321: static const StringID _cargo_string_list[NUM_LANDSCAPE][NUM_CARGO] = { tron@1316: { /* LT_NORMAL */ tron@1316: STR_PASSENGERS, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_LITERS, tron@1316: STR_ITEMS, tron@1316: STR_CRATES, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_RES_OTHER tron@1316: }, tron@1316: tron@1316: { /* LT_HILLY */ tron@1316: STR_PASSENGERS, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_LITERS, tron@1316: STR_ITEMS, tron@1316: STR_CRATES, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_RES_OTHER, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_TONS tron@1316: }, tron@1316: tron@1316: { /* LT_DESERT */ tron@1316: STR_PASSENGERS, tron@1316: STR_LITERS, tron@1316: STR_BAGS, tron@1316: STR_LITERS, tron@1316: STR_TONS, tron@1316: STR_CRATES, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_LITERS, tron@1316: STR_BAGS, tron@1316: STR_TONS tron@1316: }, tron@1316: tron@1316: { /* LT_CANDY */ tron@1316: STR_PASSENGERS, tron@1316: STR_TONS, tron@1316: STR_BAGS, tron@1316: STR_NOTHING, tron@1316: STR_NOTHING, tron@1316: STR_TONS, tron@1316: STR_TONS, tron@1316: STR_LITERS, tron@1316: STR_TONS, tron@1316: STR_NOTHING, tron@1316: STR_LITERS, tron@1316: STR_NOTHING tron@1316: } truelight@0: }; truelight@0: truelight@0: ludde@2063: // Read an int64 from the argv array. ludde@2063: static inline int64 GetInt64(const int32 **argv) ludde@2063: { ludde@2063: int64 result; ludde@2063: ludde@2063: assert(argv); ludde@2063: result = (uint32)(*argv)[0] + ((uint64)(uint32)(*argv)[1] << 32); ludde@2063: (*argv)+=2; ludde@2063: return result; ludde@2063: } ludde@2063: ludde@2063: // Read an int32 from the argv array. ludde@2063: static inline int32 GetInt32(const int32 **argv) ludde@2063: { ludde@2063: assert(argv); ludde@2063: return *(*argv)++; ludde@2063: } ludde@2063: ludde@2063: // Read an array from the argv array. ludde@2063: static inline const int32 *GetArgvPtr(const int32 **argv, int n) ludde@2063: { ludde@2063: const int32 *result; ludde@2063: assert(*argv); ludde@2063: result = *argv; ludde@2063: (*argv) += n; ludde@2063: return result; ludde@2063: } ludde@2063: ludde@2063: ludde@2055: #define NUM_BOUND_STRINGS 8 ludde@2055: ludde@2055: // Array to hold the bound strings. ludde@2055: static const char *_bound_strings[NUM_BOUND_STRINGS]; ludde@2055: ludde@2055: // This index is used to implement a "round-robin" allocating of ludde@2055: // slots for BindCString. NUM_BOUND_STRINGS slots are reserved. ludde@2055: // Which means that after NUM_BOUND_STRINGS calls to BindCString, ludde@2055: // the indices will be reused. ludde@2055: static int _bind_index; ludde@2055: tron@1321: static const char *GetStringPtr(StringID string) truelight@0: { truelight@0: return _langpack_offs[_langtab_start[string >> 11] + (string & 0x7FF)]; truelight@0: } truelight@0: ludde@2087: // The highest 8 bits of string contain the "case index". ludde@2087: // These 8 bits will only be set when FormatString wants to print ludde@2087: // the string in a different case. No one else except FormatString Darkvater@4405: // should set those bits, therefore string CANNOT be StringID, but uint32. Darkvater@4416: static char *GetStringWithArgs(char *buffr, uint string, const int32 *argv) truelight@0: { tron@2140: uint index = GB(string, 0, 11); tron@2140: uint tab = GB(string, 11, 5); peter1138@4710: char buff[512]; truelight@0: tron@2635: if (GB(string, 0, 16) == 0) error("!invalid string id 0 in GetString"); truelight@0: tron@1321: switch (tab) { tron@1321: case 4: ludde@2063: if (index >= 0xC0) ludde@2063: return GetSpecialTownNameString(buffr, index - 0xC0, GetInt32(&argv)); tron@1321: break; tron@1321: tron@1321: case 14: ludde@2063: if (index >= 0xE4) ludde@2063: return GetSpecialPlayerNameString(buffr, index - 0xE4, argv); tron@1321: break; tron@1321: ludde@2063: // User defined name tron@1321: case 15: tron@1321: return GetName(index, buffr); tron@1321: peter1138@4710: case 26: peter1138@4710: /* Include string within newgrf text (format code 81) */ peter1138@4710: if (HASBIT(index, 10)) { peter1138@4710: StringID string = GetGRFStringID(0, 0xD000 + GB(index, 0, 10)); peter1138@4710: return GetStringWithArgs(buffr, string, argv); peter1138@4710: } peter1138@4710: break; peter1138@4710: belugas@3601: case 28: peter1138@4710: GetGRFString(buff, index); peter1138@4710: return FormatString(buffr, buff, argv, 0); belugas@3601: belugas@3601: case 29: peter1138@4710: GetGRFString(buff, index + 0x800); peter1138@4710: return FormatString(buffr, buff, argv, 0); belugas@3601: belugas@3601: case 30: peter1138@4710: GetGRFString(buff, index + 0x1000); peter1138@4710: return FormatString(buffr, buff, argv, 0); belugas@3601: ludde@2063: case 31: ludde@2063: // dynamic strings. These are NOT to be passed through the formatter, ludde@2063: // but passed through verbatim. ludde@2055: if (index < (STR_SPEC_USERSTRING & 0x7FF)) { ludde@2063: return strecpy(buffr, _bound_strings[index], NULL); ludde@2055: } tron@1321: ludde@2087: return FormatString(buffr, _userstring, NULL, 0); truelight@0: } truelight@0: tron@2639: if (index >= _langtab_num[tab]) { tron@1321: error( tron@1321: "!String 0x%X is invalid. " tron@1321: "Probably because an old version of the .lng file.\n", string tron@1321: ); tron@2639: } truelight@0: tron@2635: return FormatString(buffr, GetStringPtr(GB(string, 0, 16)), argv, GB(string, 24, 8)); truelight@0: } truelight@0: ludde@2063: char *GetString(char *buffr, StringID string) ludde@2063: { ludde@2063: return GetStringWithArgs(buffr, string, (int32*)_decode_parameters); ludde@2063: } ludde@2063: ludde@2063: ludde@2055: // This function takes a C-string and allocates a temporary string ID. ludde@2055: // The duration of the bound string is valid only until the next GetString, ludde@2055: // so be careful. ludde@2055: StringID BindCString(const char *str) ludde@2055: { ludde@2055: int idx = (++_bind_index) & (NUM_BOUND_STRINGS - 1); ludde@2055: _bound_strings[idx] = str; ludde@2055: return idx + STR_SPEC_DYNSTRING; ludde@2055: } ludde@2055: ludde@2055: // This function is used to "bind" a C string to a OpenTTD dparam slot. ludde@2055: void SetDParamStr(uint n, const char *str) ludde@2055: { ludde@2055: SetDParam(n, BindCString(str)); ludde@2055: } ludde@2055: tron@1309: void InjectDParam(int amount) truelight@0: { truelight@193: memmove(_decode_parameters + amount, _decode_parameters, sizeof(_decode_parameters) - amount * sizeof(uint32)); truelight@0: } truelight@0: truelight@0: static const uint32 _divisor_table[] = { truelight@0: 1000000000, truelight@0: 100000000, truelight@0: 10000000, truelight@0: 1000000, truelight@0: truelight@0: 100000, truelight@0: 10000, truelight@0: 1000, truelight@0: 100, truelight@0: 10, truelight@0: 1 truelight@0: }; truelight@0: tron@1312: static char *FormatCommaNumber(char *buff, int32 number) truelight@0: { truelight@0: uint32 quot,divisor; truelight@0: int i; truelight@0: uint32 tot; truelight@0: uint32 num; truelight@0: truelight@0: if (number < 0) { truelight@0: *buff++ = '-'; truelight@0: number = -number; truelight@0: } truelight@0: truelight@0: num = number; truelight@0: truelight@0: tot = 0; tron@1316: for (i = 0; i != 10; i++) { truelight@0: divisor = _divisor_table[i]; truelight@0: quot = 0; truelight@0: if (num >= divisor) { truelight@0: quot = num / _divisor_table[i]; truelight@0: num = num % _divisor_table[i]; truelight@0: } tron@1316: if (tot |= quot || i == 9) { tron@1312: *buff++ = '0' + quot; tron@1316: if (i == 0 || i == 3 || i == 6) *buff++ = ','; truelight@0: } truelight@0: } truelight@0: tron@1316: *buff = '\0'; truelight@0: truelight@0: return buff; truelight@0: } truelight@0: tron@1312: static char *FormatNoCommaNumber(char *buff, int32 number) truelight@0: { truelight@0: uint32 quot,divisor; truelight@0: int i; truelight@0: uint32 tot; truelight@0: uint32 num; truelight@0: truelight@0: if (number < 0) { truelight@0: *buff++ = '-'; truelight@0: number = -number; truelight@0: } truelight@0: truelight@0: num = number; truelight@0: truelight@0: tot = 0; tron@1316: for (i = 0; i != 10; i++) { truelight@0: divisor = _divisor_table[i]; truelight@0: quot = 0; truelight@0: if (num >= divisor) { truelight@0: quot = num / _divisor_table[i]; truelight@0: num = num % _divisor_table[i]; truelight@0: } tron@1316: if (tot |= quot || i == 9) { tron@1312: *buff++ = '0' + quot; truelight@0: } truelight@0: } truelight@0: tron@1316: *buff = '\0'; truelight@0: truelight@0: return buff; truelight@0: } truelight@0: truelight@0: rubidium@4289: static char *FormatYmdString(char *buff, Date date) truelight@0: { tron@1312: const char *src; truelight@0: YearMonthDay ymd; truelight@0: rubidium@4289: ConvertDateToYMD(date, &ymd); truelight@0: tron@1316: for (src = GetStringPtr(ymd.day + STR_01AC_1ST - 1); (*buff++ = *src++) != '\0';) {} truelight@0: buff[-1] = ' '; peter1138@3832: peter1138@3832: for (src = GetStringPtr(STR_0162_JAN + ymd.month); (*buff++ = *src++) != '\0';) {} peter1138@3832: buff[-1] = ' '; truelight@0: rubidium@4293: return FormatNoCommaNumber(buff, ymd.year); truelight@0: } truelight@0: rubidium@4289: static char *FormatMonthAndYear(char *buff, Date date) truelight@0: { truelight@0: const char *src; truelight@0: YearMonthDay ymd; truelight@0: rubidium@4289: ConvertDateToYMD(date, &ymd); truelight@0: tron@1316: for (src = GetStringPtr(STR_MONTH_JAN + ymd.month); (*buff++ = *src++) != '\0';) {} truelight@0: buff[-1] = ' '; truelight@0: rubidium@4293: return FormatNoCommaNumber(buff, ymd.year); truelight@0: } truelight@0: rubidium@4289: static char *FormatTinyDate(char *buff, Date date) dominik@1097: { dominik@1097: YearMonthDay ymd; dominik@1097: rubidium@4289: ConvertDateToYMD(date, &ymd); rubidium@4293: buff += sprintf(buff, " %02i-%02i-%04i", ymd.day, ymd.month + 1, ymd.year); dominik@1097: dominik@1097: return buff; dominik@1097: } dominik@1097: tron@1312: static char *FormatGenericCurrency(char *buff, const CurrencySpec *spec, int64 number, bool compact) truelight@0: { truelight@0: const char *s; truelight@0: char c; truelight@0: char buf[40], *p; truelight@0: int j; truelight@0: truelight@0: // multiply by exchange rate truelight@0: number *= spec->rate; truelight@0: truelight@0: // convert from negative tron@1316: if (number < 0) { tron@1316: *buff++ = '-'; tron@1316: number = -number; tron@1316: } truelight@193: belugas@4602: /* Add prefix part, folowing symbol_pos specification. belugas@4602: * Here, it can can be either 0 (prefix) or 2 (both prefix anf suffix). belugas@4602: * The only remaining value is 1 (suffix), so everything that is not 1 */ belugas@4602: if (spec->symbol_pos != 1){ belugas@4377: s = spec->prefix; belugas@4377: while (s != spec->prefix + lengthof(spec->prefix) && (c = *(s++)) != '\0') *(buff)++ = c; belugas@4377: } truelight@0: truelight@0: // for huge numbers, compact the number into k or M truelight@0: if (compact) { truelight@0: compact = 0; truelight@0: if (number >= 1000000000) { truelight@0: number = (number + 500000) / 1000000; truelight@0: compact = 'M'; truelight@0: } else if (number >= 1000000) { truelight@0: number = (number + 500) / 1000; truelight@0: compact = 'k'; truelight@193: } truelight@0: } truelight@193: truelight@0: // convert to ascii number and add commas truelight@0: p = buf; truelight@0: j = 4; truelight@193: do { tron@1316: if (--j == 0) { tron@1316: *p++ = spec->separator; tron@1316: j = 3; tron@1316: } truelight@0: *p++ = '0' + number % 10; truelight@0: } while (number /= 10); truelight@0: do *buff++ = *--p; while (p != buf); truelight@0: truelight@0: if (compact) *buff++ = compact; truelight@0: belugas@4602: /* Add suffix part, folowing symbol_pos specification. belugas@4602: * Here, it can can be either 1 (suffix) or 2 (both prefix anf suffix). belugas@4602: * The only remaining value is 1 (prefix), so everything that is not 0 */ belugas@4377: if (spec->symbol_pos != 0) { belugas@4377: s = spec->suffix; belugas@4377: while (s != spec->suffix + lengthof(spec->suffix) && (c = *(s++)) != '\0') *(buff++) = c; belugas@4377: } truelight@0: truelight@0: return buff; truelight@0: } truelight@0: ludde@2082: static int DeterminePluralForm(int32 n) ludde@2082: { ludde@2082: // The absolute value determines plurality ludde@2082: if (n < 0) n = -n; ludde@2082: tron@2952: switch (_langpack->plural_form) { ludde@2082: // Two forms, singular used for one only ludde@2082: // Used in: ludde@2082: // Danish, Dutch, English, German, Norwegian, Swedish, Estonian, Finnish, ludde@2082: // Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto ludde@2082: case 0: ludde@2082: default: ludde@2082: return n != 1; ludde@2082: ludde@2082: // Only one form ludde@2082: // Used in: ludde@2082: // Hungarian, Japanese, Korean, Turkish ludde@2082: case 1: ludde@2082: return 0; ludde@2082: ludde@2082: // Two forms, singular used for zero and one ludde@2082: // Used in: ludde@2082: // French, Brazilian Portuguese ludde@2082: case 2: ludde@2082: return n > 1; ludde@2082: ludde@2082: // Three forms, special case for zero ludde@2082: // Used in: ludde@2082: // Latvian ludde@2082: case 3: ludde@2082: return n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2; ludde@2082: ludde@2082: // Three forms, special case for one and two ludde@2082: // Used in: ludde@2082: // Gaelige (Irish) ludde@2082: case 4: ludde@2082: return n==1 ? 0 : n==2 ? 1 : 2; ludde@2082: ludde@2082: // Three forms, special case for numbers ending in 1[2-9] ludde@2082: // Used in: ludde@2082: // Lithuanian ludde@2082: case 5: ludde@2082: return n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2; ludde@2082: ludde@2082: // Three forms, special cases for numbers ending in 1 and 2, 3, 4, except those ending in 1[1-4] ludde@2082: // Used in: ludde@2082: // Croatian, Czech, Russian, Slovak, Ukrainian ludde@2082: case 6: ludde@2082: return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; ludde@2082: ludde@2082: // Three forms, special case for one and some numbers ending in 2, 3, or 4 ludde@2082: // Used in: ludde@2082: // Polish ludde@2082: case 7: ludde@2082: return n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; ludde@2082: ludde@2082: // Four forms, special case for one and all numbers ending in 02, 03, or 04 ludde@2082: // Used in: ludde@2082: // Slovenian ludde@2082: case 8: ludde@2082: return n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3; ludde@2082: } ludde@2082: } ludde@2082: ludde@2082: static const char *ParseStringChoice(const char *b, uint form, char *dst, int *dstlen) ludde@2082: { ludde@2082: // {Length of each string} {each string} ludde@2082: uint n = (byte)*b++; ludde@2082: uint pos,i, mylen=0,mypos=0; tron@2952: tron@2952: for (i = pos = 0; i != n; i++) { ludde@2082: uint len = (byte)*b++; ludde@2082: if (i == form) { ludde@2082: mypos = pos; ludde@2082: mylen = len; ludde@2082: } ludde@2082: pos += len; ludde@2082: } ludde@2082: *dstlen = mylen; ludde@2082: memcpy(dst, b + mypos, mylen); ludde@2082: return b + pos; ludde@2082: } ludde@2082: peter1138@3342: typedef struct Units { peter1138@3342: int s_m; ///< Multiplier for velocity peter1138@3342: int s_s; ///< Shift for velocity peter1138@3342: StringID velocity; ///< String for velocity peter1138@3342: int p_m; ///< Multiplier for power peter1138@3342: int p_s; ///< Shift for power peter1138@3342: StringID power; ///< String for velocity peter1138@3342: int w_m; ///< Multiplier for weight peter1138@3342: int w_s; ///< Shift for weight peter1138@3342: StringID s_weight; ///< Short string for weight peter1138@3342: StringID l_weight; ///< Long string for weight peter1138@3342: int v_m; ///< Multiplier for volume peter1138@3342: int v_s; ///< Shift for volume peter1138@3342: StringID s_volume; ///< Short string for volume peter1138@3342: StringID l_volume; ///< Long string for volume peter1138@3489: int f_m; ///< Multiplier for force peter1138@3489: int f_s; ///< Shift for force peter1138@3489: StringID force; ///< String for force peter1138@3342: } Units; peter1138@3342: peter1138@3489: /* Unit conversions */ peter1138@3342: static const Units units[] = { peter1138@3489: { // Imperial (Original, mph, hp, metric ton, litre, metric ton force) peter1138@3477: 10, 4, STR_UNITS_VELOCITY_IMPERIAL, peter1138@3342: 1, 0, STR_UNITS_POWER_IMPERIAL, peter1138@3342: 1, 0, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, peter1138@3342: 1000, 0, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, peter1138@3489: 835, 13, STR_UNITS_FORCE_METRIC, peter1138@3342: }, peter1138@3489: { // Metric (km/h, hp, metric ton, litre, metric ton force) peter1138@3477: 1, 0, STR_UNITS_VELOCITY_METRIC, peter1138@3342: 1, 0, STR_UNITS_POWER_METRIC, peter1138@3342: 1, 0, STR_UNITS_WEIGHT_SHORT_METRIC, STR_UNITS_WEIGHT_LONG_METRIC, peter1138@3342: 1000, 0, STR_UNITS_VOLUME_SHORT_METRIC, STR_UNITS_VOLUME_LONG_METRIC, peter1138@3489: 835, 13, STR_UNITS_FORCE_METRIC, peter1138@3342: }, peter1138@3489: { // SI (m/s, kilowatt, kilogram, cubic metres, kilonewton) peter1138@3477: 284, 10, STR_UNITS_VELOCITY_SI, peter1138@3342: 764, 10, STR_UNITS_POWER_SI, peter1138@3342: 1000, 0, STR_UNITS_WEIGHT_SHORT_SI, STR_UNITS_WEIGHT_LONG_SI, peter1138@3485: 1, 0, STR_UNITS_VOLUME_SHORT_SI, STR_UNITS_VOLUME_LONG_SI, peter1138@3489: 1, 0, STR_UNITS_FORCE_SI, peter1138@3342: }, peter1138@3342: }; ludde@2082: ludde@2087: static char *FormatString(char *buff, const char *str, const int32 *argv, uint casei) truelight@0: { truelight@0: byte b; ludde@2063: const int32 *argv_orig = argv; ludde@2087: uint modifier = 0; truelight@0: tron@1316: while ((b = *str++) != '\0') { tron@1316: switch (b) { truelight@0: case 0x1: // {SETX} truelight@0: *buff++ = b; truelight@0: *buff++ = *str++; truelight@0: break; truelight@0: case 0x2: // {SETXY} truelight@0: *buff++ = b; truelight@0: *buff++ = *str++; truelight@0: *buff++ = *str++; truelight@0: break; tron@2410: truelight@0: case 0x81: // {STRINL} Darkvater@2966: buff = GetStringWithArgs(buff, ReadLE16Unaligned(str), argv); truelight@0: str += 2; truelight@0: break; truelight@0: case 0x82: // {DATE_LONG} ludde@2063: buff = FormatYmdString(buff, GetInt32(&argv)); truelight@0: break; truelight@0: case 0x83: // {DATE_SHORT} ludde@2063: buff = FormatMonthAndYear(buff, GetInt32(&argv)); truelight@0: break; truelight@0: case 0x84: {// {VELOCITY} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].s_m >> units[_opt_ptr->units].s_s; peter1138@3342: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].velocity), args, modifier >> 24); peter1138@3342: modifier = 0; truelight@0: break; truelight@0: } truelight@193: // 0x85 is used as escape character.. truelight@0: case 0x85: tron@1316: switch (*str++) { darkvater@236: case 0: /* {CURRCOMPACT} */ tron@2468: buff = FormatGenericCurrency(buff, _currency, GetInt32(&argv), true); truelight@0: break; darkvater@236: case 2: /* {REV} */ tron@1853: buff = strecpy(buff, _openttd_revision, NULL); truelight@0: break; darkvater@236: case 3: { /* {SHORTCARGO} */ darkvater@236: // Short description of cargotypes. Layout: truelight@0: // 8-bit = cargo type truelight@0: // 16-bit = cargo count belugas@3616: StringID cargo_str = _cargo_types_base_values[_opt_ptr->landscape].units_volume[GetInt32(&argv)]; peter1138@3342: switch (cargo_str) { peter1138@3342: case STR_TONS: { peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].w_m >> units[_opt_ptr->units].w_s; peter1138@3342: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].l_weight), args, modifier >> 24); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@3342: case STR_LITERS: { peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].v_m >> units[_opt_ptr->units].v_s; peter1138@3342: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].l_volume), args, modifier >> 24); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@3342: default: peter1138@3342: buff = FormatCommaNumber(buff, GetInt32(&argv)); peter1138@3342: buff = strecpy(buff, " ", NULL); peter1138@3342: buff = strecpy(buff, GetStringPtr(cargo_str), NULL); peter1138@3342: break; peter1138@3342: } tron@1316: } break; ludde@2063: case 4: {/* {CURRCOMPACT64} */ darkvater@236: // 64 bit compact currency-unit tron@2468: buff = FormatGenericCurrency(buff, _currency, GetInt64(&argv), true); truelight@0: break; ludde@2063: } ludde@2063: case 5: { /* {STRING1} */ ludde@2063: // String that consumes ONE argument ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 1)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: case 6: { /* {STRING2} */ ludde@2063: // String that consumes TWO arguments ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 2)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: case 7: { /* {STRING3} */ ludde@2063: // String that consumes THREE arguments ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 3)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: case 8: { /* {STRING4} */ ludde@2063: // String that consumes FOUR arguments ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 4)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: case 9: { /* {STRING5} */ ludde@2063: // String that consumes FIVE arguments ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: buff = GetStringWithArgs(buff, str, GetArgvPtr(&argv, 5)); ludde@2087: modifier = 0; ludde@2063: break; ludde@2063: } ludde@2063: ludde@2070: case 10: { /* {STATIONFEATURES} */ ludde@2063: buff = StationGetSpecialString(buff, GetInt32(&argv)); ludde@2063: break; ludde@2063: } truelight@0: ludde@2070: case 11: { /* {INDUSTRY} */ tron@3033: const Industry* i = GetIndustry(GetInt32(&argv)); ludde@2070: int32 args[2]; ludde@2070: ludde@2070: // industry not valid anymore? truelight@4346: if (!IsValidIndustry(i)) break; ludde@2070: ludde@2070: // First print the town name and the industry type name ludde@2070: // The string STR_INDUSTRY_PATTERN controls the formatting ludde@2070: args[0] = i->town->index; ludde@2070: args[1] = i->type + STR_4802_COAL_MINE; ludde@2087: buff = FormatString(buff, GetStringPtr(STR_INDUSTRY_FORMAT), args, modifier >> 24); ludde@2087: modifier = 0; ludde@2070: break; ludde@2070: } ludde@2070: ludde@2084: case 12: { // {VOLUME} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].v_m >> units[_opt_ptr->units].v_s; peter1138@3342: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].l_volume), args, modifier >> 24); ludde@2087: modifier = 0; ludde@2084: break; ludde@2084: } ludde@2084: ludde@2084: case 13: { // {G 0 Der Die Das} tron@2643: const byte* s = (const byte*)GetStringPtr(argv_orig[(byte)*str++]); // contains the string that determines gender. ludde@2084: int len; ludde@2084: int gender = 0; tron@2639: if (s != NULL && s[0] == 0x87) gender = s[1]; ludde@2084: str = ParseStringChoice(str, gender, buff, &len); ludde@2084: buff += len; ludde@2084: break; ludde@2084: } ludde@2084: ludde@2087: case 14: { // {DATE_TINY} ludde@2087: buff = FormatTinyDate(buff, GetInt32(&argv)); ludde@2087: break; ludde@2087: } ludde@2087: ludde@2087: case 15: { // {CARGO} ludde@2087: // Layout now is: ludde@2087: // 8bit - cargo type ludde@2087: // 16-bit - cargo count ludde@2087: StringID cargo_str = _cargoc.names_long[GetInt32(&argv)]; ludde@2131: buff = GetStringWithArgs(buff, cargo_str, argv++); ludde@2087: break; ludde@2087: } ludde@2087: peter1138@3342: case 16: { // {POWER} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].p_m >> units[_opt_ptr->units].p_s; peter1138@3342: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].power), args, modifier >> 24); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@3342: case 17: { // {VOLUME_S} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].v_m >> units[_opt_ptr->units].v_s; peter1138@3342: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].s_volume), args, modifier >> 24); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@3342: case 18: { // {WEIGHT} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].w_m >> units[_opt_ptr->units].w_s; peter1138@3342: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].l_weight), args, modifier >> 24); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@3342: case 19: { // {WEIGHT_S} peter1138@3342: int32 args[1]; peter1138@3342: assert(_opt_ptr->units < lengthof(units)); peter1138@3342: args[0] = GetInt32(&argv) * units[_opt_ptr->units].w_m >> units[_opt_ptr->units].w_s; peter1138@3342: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].s_weight), args, modifier >> 24); peter1138@3342: modifier = 0; peter1138@3342: break; peter1138@3342: } peter1138@3342: peter1138@3489: case 20: { // {FORCE} peter1138@3489: int32 args[1]; peter1138@3489: assert(_opt_ptr->units < lengthof(units)); peter1138@3489: args[0] = GetInt32(&argv) * units[_opt_ptr->units].f_m >> units[_opt_ptr->units].f_s; peter1138@3489: buff = FormatString(buff, GetStringPtr(units[_opt_ptr->units].force), args, modifier >> 24); peter1138@3489: modifier = 0; peter1138@3489: break; peter1138@3489: } peter1138@3489: truelight@0: default: truelight@0: error("!invalid escape sequence in string"); truelight@0: } truelight@0: break; truelight@0: truelight@0: case 0x86: // {SKIP} ludde@2063: argv++; truelight@0: break; ludde@2084: ludde@2084: // This sets up the gender for the string. ludde@2087: // We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. ludde@2084: case 0x87: // {GENDER 0} ludde@2084: str++; truelight@0: break; truelight@193: ludde@2063: case 0x88: {// {STRING} ludde@2087: uint str = modifier + GetInt32(&argv); ludde@2063: // WARNING. It's prohibited for the included string to consume any arguments. ludde@2063: // For included strings that consume argument, you should use STRING1, STRING2 etc. ludde@2063: // To debug stuff you can set argv to NULL and it will tell you ludde@2063: buff = GetStringWithArgs(buff, str, argv); ludde@2087: modifier = 0; truelight@0: break; ludde@2063: } truelight@0: tron@2410: case 0x8B: // {COMMA} tron@2410: buff = FormatCommaNumber(buff, GetInt32(&argv)); tron@2410: break; tron@2410: tron@2410: case 0x8C: // Move argument pointer tron@2410: argv = argv_orig + (byte)*str++; tron@2410: break; tron@2410: tron@2410: case 0x8D: { // {P} tron@2410: int32 v = argv_orig[(byte)*str++]; // contains the number that determines plural tron@2410: int len; tron@2410: str = ParseStringChoice(str, DeterminePluralForm(v), buff, &len); tron@2410: buff += len; tron@2410: break; tron@2410: } tron@2410: tron@2410: case 0x8E: // {NUM} tron@2410: buff = FormatNoCommaNumber(buff, GetInt32(&argv)); tron@2410: break; tron@2410: tron@2410: case 0x8F: // {CURRENCY} tron@2468: buff = FormatGenericCurrency(buff, _currency, GetInt32(&argv), false); tron@2410: break; tron@2410: ludde@2087: case 0x99: { // {WAYPOINT} ludde@2087: int32 temp[2]; ludde@2087: Waypoint *wp = GetWaypoint(GetInt32(&argv)); ludde@2087: StringID str; ludde@2087: if (wp->string != STR_NULL) { ludde@2087: str = wp->string; ludde@2087: } else { ludde@2087: temp[0] = wp->town_index; ludde@2087: temp[1] = wp->town_cn + 1; ludde@2087: str = wp->town_cn == 0 ? STR_WAYPOINTNAME_CITY : STR_WAYPOINTNAME_CITY_SERIAL; ludde@2087: } ludde@2087: buff = GetStringWithArgs(buff, str, temp); ludde@2087: } break; truelight@0: truelight@0: case 0x9A: { // {STATION} tron@2630: const Station* st = GetStation(GetInt32(&argv)); ludde@2063: int32 temp[2]; ludde@2063: truelight@4346: if (!IsValidStation(st)) { // station doesn't exist anymore ludde@2063: buff = GetStringWithArgs(buff, STR_UNKNOWN_DESTINATION, NULL); darkvater@64: break; darkvater@64: } ludde@2063: temp[0] = st->town->townnametype; ludde@2063: temp[1] = st->town->townnameparts; ludde@2063: buff = GetStringWithArgs(buff, st->string_id, temp); truelight@0: break; truelight@0: } truelight@0: case 0x9B: { // {TOWN} tron@2630: const Town* t = GetTown(GetInt32(&argv)); ludde@2063: int32 temp[1]; tron@2639: truelight@4346: assert(IsValidTown(t)); tron@2630: ludde@2063: temp[0] = t->townnameparts; ludde@2063: buff = GetStringWithArgs(buff, t->townnametype, temp); truelight@0: break; truelight@0: } truelight@0: truelight@0: case 0x9C: { // {CURRENCY64} tron@2468: buff = FormatGenericCurrency(buff, _currency, GetInt64(&argv), false); truelight@0: break; truelight@0: } truelight@0: ludde@2087: case 0x9D: { // {SETCASE} tron@2639: // This is a pseudo command, it's outputted when someone does {STRING.ack} ludde@2087: // The modifier is added to all subsequent GetStringWithArgs that accept the modifier. ludde@2087: modifier = (byte)*str++ << 24; ludde@2087: break; ludde@2087: } ludde@2087: ludde@2087: case 0x9E: { // {Used to implement case switching} ludde@2087: // <0x9E> ludde@2087: // Each LEN is printed using 2 bytes in big endian order. ludde@2087: uint num = (byte)*str++; ludde@2087: while (num) { Darkvater@2107: if ((byte)str[0] == casei) { ludde@2087: // Found the case, adjust str pointer and continue ludde@2087: str += 3; ludde@2087: break; ludde@2087: } ludde@2087: // Otherwise skip to the next case ludde@2087: str += 3 + (str[1] << 8) + str[2]; ludde@2087: num--; truelight@0: } dominik@1097: break; dominik@1097: } dominik@1097: truelight@0: default: truelight@0: *buff++ = b; truelight@0: } truelight@0: } tron@1316: *buff = '\0'; truelight@0: return buff; truelight@0: } truelight@0: truelight@0: ludde@2063: static char *StationGetSpecialString(char *buff, int x) truelight@0: { tron@2353: if (x & 0x01) *buff++ = '\x94'; tron@2353: if (x & 0x02) *buff++ = '\x95'; tron@2353: if (x & 0x04) *buff++ = '\x96'; tron@2353: if (x & 0x08) *buff++ = '\x97'; tron@2353: if (x & 0x10) *buff++ = '\x98'; tron@1316: *buff = '\0'; truelight@0: return buff; truelight@0: } truelight@0: ludde@2063: static char *GetSpecialTownNameString(char *buff, int ind, uint32 seed) tron@1316: { ludde@2063: _town_name_generators[ind](buff, seed); truelight@0: tron@1316: while (*buff != '\0') buff++; truelight@0: return buff; truelight@0: } truelight@0: tron@2650: static const char* const _silly_company_names[] = { truelight@0: "Bloggs Brothers", truelight@0: "Tiny Transport Ltd.", truelight@0: "Express Travel", truelight@0: "Comfy-Coach & Co.", truelight@0: "Crush & Bump Ltd.", truelight@0: "Broken & Late Ltd.", truelight@0: "Sam Speedy & Son", truelight@0: "Supersonic Travel", truelight@0: "Mike's Motors", truelight@0: "Lightning International", truelight@0: "Pannik & Loozit Ltd.", truelight@0: "Inter-City Transport", tron@2650: "Getout & Pushit Ltd." truelight@0: }; truelight@0: tron@2650: static const char* const _surname_list[] = { truelight@0: "Adams", truelight@0: "Allan", truelight@0: "Baker", truelight@0: "Bigwig", truelight@0: "Black", truelight@0: "Bloggs", truelight@0: "Brown", truelight@0: "Campbell", truelight@0: "Gordon", truelight@0: "Hamilton", truelight@0: "Hawthorn", truelight@0: "Higgins", truelight@0: "Green", truelight@0: "Gribble", truelight@0: "Jones", truelight@0: "McAlpine", truelight@0: "MacDonald", truelight@0: "McIntosh", truelight@0: "Muir", truelight@0: "Murphy", truelight@0: "Nelson", truelight@0: "O'Donnell", truelight@0: "Parker", truelight@0: "Phillips", truelight@0: "Pilkington", truelight@0: "Quigley", truelight@0: "Sharkey", truelight@0: "Thomson", tron@2650: "Watkins" tron@2650: }; tron@2650: tron@2650: static const char* const _silly_surname_list[] = { truelight@0: "Grumpy", truelight@0: "Dozy", truelight@0: "Speedy", truelight@0: "Nosey", truelight@0: "Dribble", truelight@0: "Mushroom", truelight@0: "Cabbage", truelight@0: "Sniffle", truelight@0: "Fishy", truelight@0: "Swindle", truelight@0: "Sneaky", tron@2650: "Nutkins" truelight@0: }; truelight@0: tron@1312: static const char _initial_name_letters[] = { tron@1321: 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', tron@1321: 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W', truelight@0: }; truelight@0: ludde@2063: static char *GenAndCoName(char *buff, uint32 arg) truelight@0: { tron@2650: const char* const* base; tron@2650: uint num; truelight@0: Darkvater@1704: if (_opt_ptr->landscape == LT_CANDY) { tron@2650: base = _silly_surname_list; tron@2650: num = lengthof(_silly_surname_list); tron@2650: } else { tron@2650: base = _surname_list; tron@2650: num = lengthof(_surname_list); truelight@0: } truelight@0: tron@2650: buff = strecpy(buff, base[num * GB(arg, 16, 8) >> 8], NULL); tron@1853: buff = strecpy(buff, " & Co.", NULL); truelight@0: truelight@0: return buff; truelight@0: } truelight@0: ludde@2063: static char *GenPresidentName(char *buff, uint32 x) truelight@0: { tron@2650: const char* const* base; tron@2650: uint num; tron@2650: uint i; truelight@0: tron@2150: buff[0] = _initial_name_letters[sizeof(_initial_name_letters) * GB(x, 0, 8) >> 8]; truelight@0: buff[1] = '.'; darkvater@233: buff[2] = ' '; // Insert a space after initial and period "I. Firstname" instead of "I.Firstname" darkvater@233: buff += 3; truelight@0: tron@2150: i = (sizeof(_initial_name_letters) + 35) * GB(x, 8, 8) >> 8; truelight@0: if (i < sizeof(_initial_name_letters)) { truelight@0: buff[0] = _initial_name_letters[i]; truelight@0: buff[1] = '.'; darkvater@233: buff[2] = ' '; // Insert a space after initial and period "I. J. Firstname" instead of "I.J.Firstname" darkvater@233: buff += 3; truelight@0: } truelight@0: Darkvater@1704: if (_opt_ptr->landscape == LT_CANDY) { tron@2650: base = _silly_surname_list; tron@2650: num = lengthof(_silly_surname_list); tron@2650: } else { tron@2650: base = _surname_list; tron@2650: num = lengthof(_surname_list); truelight@0: } truelight@0: tron@2650: buff = strecpy(buff, base[num * GB(x, 16, 8) >> 8], NULL); truelight@0: truelight@0: return buff; truelight@0: } truelight@0: ludde@2063: static char *GetSpecialPlayerNameString(char *buff, int ind, const int32 *argv) truelight@0: { tron@1316: switch (ind) { tron@1321: case 1: // not used ludde@2063: return strecpy(buff, _silly_company_names[GetInt32(&argv) & 0xFFFF], NULL); truelight@0: tron@1321: case 2: // used for Foobar & Co company names ludde@2063: return GenAndCoName(buff, GetInt32(&argv)); truelight@0: tron@1321: case 3: // President name ludde@2063: return GenPresidentName(buff, GetInt32(&argv)); truelight@0: tron@1321: case 4: // song names belugas@4120: return strecpy(buff, origin_songs_specs[GetInt32(&argv) - 1].song_name, NULL); truelight@0: } truelight@0: truelight@0: // town name? tron@1316: if (IS_INT_INSIDE(ind - 6, 0, SPECSTR_TOWNNAME_LAST-SPECSTR_TOWNNAME_START + 1)) { ludde@2063: buff = GetSpecialTownNameString(buff, ind - 6, GetInt32(&argv)); tron@1853: return strecpy(buff, " Transport", NULL); truelight@0: } truelight@0: truelight@0: // language name? truelight@0: if (IS_INT_INSIDE(ind, (SPECSTR_LANGUAGE_START - 0x70E4), (SPECSTR_LANGUAGE_END - 0x70E4) + 1)) { truelight@0: int i = ind - (SPECSTR_LANGUAGE_START - 0x70E4); tron@1853: return strecpy(buff, tron@1853: i == _dynlang.curr ? _langpack->own_name : _dynlang.ent[i].name, NULL); truelight@0: } truelight@0: truelight@0: // resolution size? truelight@0: if (IS_INT_INSIDE(ind, (SPECSTR_RESOLUTION_START - 0x70E4), (SPECSTR_RESOLUTION_END - 0x70E4) + 1)) { truelight@0: int i = ind - (SPECSTR_RESOLUTION_START - 0x70E4); truelight@0: return buff + sprintf(buff, "%dx%d", _resolutions[i][0], _resolutions[i][1]); truelight@0: } truelight@0: truelight@0: // screenshot format name? truelight@0: if (IS_INT_INSIDE(ind, (SPECSTR_SCREENSHOT_START - 0x70E4), (SPECSTR_SCREENSHOT_END - 0x70E4) + 1)) { truelight@0: int i = ind - (SPECSTR_SCREENSHOT_START - 0x70E4); tron@1853: return strecpy(buff, GetScreenshotFormatDesc(i), NULL); truelight@0: } truelight@0: truelight@0: assert(0); truelight@0: return NULL; truelight@0: } truelight@0: truelight@0: // remap a string ID from the old format to the new format truelight@0: StringID RemapOldStringID(StringID s) truelight@0: { tron@1321: switch (s) { tron@1321: case 0x0006: return STR_SV_EMPTY; tron@1321: case 0x7000: return STR_SV_UNNAMED; tron@1321: case 0x70E4: return SPECSTR_PLAYERNAME_ENGLISH; tron@1321: case 0x70E9: return SPECSTR_PLAYERNAME_ENGLISH; tron@1321: case 0x8864: return STR_SV_TRAIN_NAME; tron@1321: case 0x902B: return STR_SV_ROADVEH_NAME; tron@1321: case 0x9830: return STR_SV_SHIP_NAME; tron@1321: case 0xA02F: return STR_SV_AIRCRAFT_NAME; tron@1321: tron@1321: default: tron@2951: if (IS_INT_INSIDE(s, 0x300F, 0x3030)) { tron@1321: return s - 0x300F + STR_SV_STNAME; tron@2951: } else { tron@1321: return s; tron@2951: } tron@1321: } truelight@0: } truelight@0: tron@1316: bool ReadLanguagePack(int lang_index) tron@1316: { truelight@0: int tot_count, i; tron@1319: LanguagePack *lang_pack; truelight@0: size_t len; tron@1312: char **langpack_offs; tron@1312: char *s; truelight@0: truelight@0: { truelight@0: char *lang = str_fmt("%s%s", _path.lang_dir, _dynlang.ent[lang_index].file); truelight@0: lang_pack = ReadFileToMem(lang, &len, 100000); truelight@0: free(lang); truelight@0: } truelight@0: if (lang_pack == NULL) return false; tron@1319: if (len < sizeof(LanguagePack) || tron@1319: lang_pack->ident != TO_LE32(LANGUAGE_PACK_IDENT) || tron@1319: lang_pack->version != TO_LE32(LANGUAGE_PACK_VERSION)) { truelight@0: free(lang_pack); truelight@0: return false; truelight@0: } truelight@193: truelight@0: #if defined(TTD_BIG_ENDIAN) tron@1316: for (i = 0; i != 32; i++) { Darkvater@2966: lang_pack->offsets[i] = ReadLE16Aligned(&lang_pack->offsets[i]); truelight@0: } truelight@0: #endif truelight@0: truelight@0: tot_count = 0; tron@1316: for (i = 0; i != 32; i++) { tron@1319: uint num = lang_pack->offsets[i]; truelight@0: _langtab_start[i] = tot_count; truelight@0: _langtab_num[i] = num; truelight@0: tot_count += num; truelight@0: } truelight@0: truelight@0: // Allocate offsets tron@1312: langpack_offs = malloc(tot_count * sizeof(*langpack_offs)); truelight@0: truelight@0: // Fill offsets tron@1319: s = lang_pack->data; tron@1316: for (i = 0; i != tot_count; i++) { tron@1312: len = (byte)*s; tron@1316: *s++ = '\0'; // zero terminate the string before. tron@1316: if (len >= 0xC0) len = ((len & 0x3F) << 8) + (byte)*s++; truelight@0: langpack_offs[i] = s; truelight@0: s += len; truelight@0: } truelight@0: tron@1321: free(_langpack); truelight@0: _langpack = lang_pack; truelight@0: tron@1321: free(_langpack_offs); truelight@0: _langpack_offs = langpack_offs; truelight@0: truelight@0: ttd_strlcpy(_dynlang.curr_file, _dynlang.ent[lang_index].file, sizeof(_dynlang.curr_file)); truelight@0: truelight@0: _dynlang.curr = lang_index; belugas@3601: SetCurrentGrfLangID(_langpack->isocode); truelight@0: return true; truelight@0: } truelight@0: Darkvater@3329: /** Determine the current charset based on the environment Darkvater@3329: * First check some default values, after this one we passed ourselves Darkvater@3329: * and if none exist return the value for $LANG Darkvater@3329: * @param environment variable to check conditionally if default ones are not Darkvater@3329: * set. Pass NULL if you don't want additional checks. Darkvater@3329: * @return return string containing current charset, or NULL if not-determinable */ Darkvater@3329: const char *GetCurrentLocale(const char *param) Darkvater@3329: { Darkvater@3329: const char *env; Darkvater@3329: Darkvater@3329: env = getenv("LANGUAGE"); Darkvater@3329: if (env != NULL) return env; Darkvater@3329: Darkvater@3329: env = getenv("LC_ALL"); Darkvater@3329: if (env != NULL) return env; Darkvater@3329: Darkvater@3329: if (param != NULL) { Darkvater@3329: env = getenv(param); Darkvater@3329: if (env != NULL) return env; Darkvater@3329: } Darkvater@3329: Darkvater@3329: return getenv("LANG"); Darkvater@3329: } Darkvater@3329: Darkvater@4219: static int CDECL LanguageCompareFunc(const void *a, const void *b) Darkvater@4219: { Darkvater@4219: return strcmp(*(const char* const *)a, *(const char* const *)b); Darkvater@4219: } Darkvater@4219: Darkvater@4219: static int GetLanguageList(char **languages, int max) Darkvater@4219: { Darkvater@4219: DIR *dir; Darkvater@4219: struct dirent *dirent; Darkvater@4219: int num = 0; Darkvater@4219: Darkvater@4219: dir = opendir(_path.lang_dir); Darkvater@4219: if (dir != NULL) { Darkvater@4219: while ((dirent = readdir(dir)) != NULL) { Darkvater@4219: char *t = strrchr(dirent->d_name, '.'); Darkvater@4219: Darkvater@4219: if (t != NULL && strcmp(t, ".lng") == 0) { Darkvater@4219: languages[num++] = strdup(dirent->d_name); Darkvater@4219: if (num == max) break; Darkvater@4219: } Darkvater@4219: } Darkvater@4219: closedir(dir); Darkvater@4219: } Darkvater@4219: Darkvater@4219: qsort(languages, num, sizeof(char*), LanguageCompareFunc); Darkvater@4219: return num; Darkvater@4219: } Darkvater@4219: truelight@0: // make a list of the available language packs. put the data in _dynlang struct. tron@1093: void InitializeLanguagePacks(void) truelight@0: { truelight@0: DynamicLanguages *dl = &_dynlang; tron@1321: int i; tron@1321: int n; tron@1321: int m; tron@1321: int def; tron@4505: int def2; tron@2257: int fallback; tron@1319: LanguagePack hdr; truelight@0: FILE *in; truelight@0: char *files[32]; tron@4505: const char* lang; tron@2257: tron@4505: lang = GetCurrentLocale("LC_MESSAGES"); tron@4505: if (lang == NULL) lang = "en_GB"; tron@2257: truelight@0: n = GetLanguageList(files, lengthof(files)); truelight@0: tron@2257: def = -1; tron@4505: def2 = -1; tron@2257: fallback = 0; truelight@0: truelight@0: // go through the language files and make sure that they are valid. tron@1316: for (i = m = 0; i != n; i++) { truelight@4321: size_t j; tron@1321: truelight@0: char *s = str_fmt("%s%s", _path.lang_dir, files[i]); truelight@0: in = fopen(s, "rb"); truelight@0: free(s); tron@1316: if (in == NULL || truelight@0: (j = fread(&hdr, sizeof(hdr), 1, in), fclose(in), j) != 1 || truelight@0: hdr.ident != TO_LE32(LANGUAGE_PACK_IDENT) || truelight@0: hdr.version != TO_LE32(LANGUAGE_PACK_VERSION)) { truelight@0: free(files[i]); truelight@0: continue; truelight@0: } truelight@0: truelight@0: dl->ent[m].file = files[i]; truelight@0: dl->ent[m].name = strdup(hdr.name); truelight@193: tron@4505: if (strcmp(hdr.isocode, "en_GB") == 0) fallback = m; tron@4505: if (strncmp(hdr.isocode, lang, 2) == 0) def2 = m; tron@4505: if (strncmp(hdr.isocode, lang, 5) == 0) def = m; truelight@0: truelight@0: m++; truelight@0: } tron@4505: if (def == -1) def = (def2 != -1 ? def2 : fallback); truelight@0: truelight@0: if (m == 0) truelight@0: error(n == 0 ? "No available language packs" : "Invalid version of language packs"); truelight@193: truelight@0: dl->num = m; tron@3033: for (i = 0; i != dl->num; i++) dl->dropdown[i] = SPECSTR_LANGUAGE_START + i; truelight@0: dl->dropdown[i] = INVALID_STRING_ID; truelight@0: tron@1316: for (i = 0; i != dl->num; i++) tron@1316: if (strcmp(dl->ent[i].file, dl->curr_file) == 0) { truelight@0: def = i; truelight@0: break; truelight@0: } truelight@0: truelight@0: if (!ReadLanguagePack(def)) truelight@0: error("can't read language pack '%s'", dl->ent[def].file); Darkvater@2075: }