1 #include "building/type.h"
2 #include "core/lang.h"
3 
4 #include "core/buffer.h"
5 #include "core/file.h"
6 #include "core/io.h"
7 #include "core/log.h"
8 #include "core/string.h"
9 #include "scenario/building.h"
10 #include "translation/translation.h"
11 
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #define MAX_TEXT_ENTRIES 1000
16 #define MAX_TEXT_DATA 200000
17 #define MIN_TEXT_SIZE (28 + MAX_TEXT_ENTRIES * 8)
18 #define MAX_TEXT_SIZE (MIN_TEXT_SIZE + MAX_TEXT_DATA)
19 
20 #define MAX_MESSAGE_ENTRIES 400
21 #define MAX_MESSAGE_DATA 460000
22 #define MIN_MESSAGE_SIZE 32024
23 #define MAX_MESSAGE_SIZE (MIN_MESSAGE_SIZE + MAX_MESSAGE_DATA)
24 
25 #define BUFFER_SIZE 400000
26 
27 #define FILE_TEXT_ENG "c3.eng"
28 #define FILE_MM_ENG "c3_mm.eng"
29 #define FILE_TEXT_RUS "c3.rus"
30 #define FILE_MM_RUS "c3_mm.rus"
31 #define FILE_EDITOR_TEXT_ENG "c3_map.eng"
32 #define FILE_EDITOR_MM_ENG "c3_map_mm.eng"
33 
34 static struct {
35     struct {
36         int32_t offset;
37         int32_t in_use;
38     } text_entries[MAX_TEXT_ENTRIES];
39     uint8_t text_data[MAX_TEXT_DATA];
40 
41     lang_message message_entries[MAX_MESSAGE_ENTRIES];
42     uint8_t message_data[MAX_MESSAGE_DATA];
43 } data;
44 
file_exists_in_dir(const char * dir,const char * file)45 static int file_exists_in_dir(const char *dir, const char *file)
46 {
47     char path[2 * FILE_NAME_MAX];
48     path[2 * FILE_NAME_MAX - 1] = 0;
49     strncpy(path, dir, 2 * FILE_NAME_MAX - 1);
50     strncat(path, "/", 2 * FILE_NAME_MAX - 1);
51     strncat(path, file, 2 * FILE_NAME_MAX - 1);
52     return file_exists(path, NOT_LOCALIZED);
53 }
54 
lang_dir_is_valid(const char * dir)55 int lang_dir_is_valid(const char *dir)
56 {
57     if (file_exists_in_dir(dir, FILE_TEXT_ENG) && file_exists_in_dir(dir, FILE_MM_ENG)) {
58         return 1;
59     }
60     if (file_exists_in_dir(dir, FILE_TEXT_RUS) && file_exists_in_dir(dir, FILE_MM_RUS)) {
61         return 1;
62     }
63     return 0;
64 }
65 
parse_text(buffer * buf)66 static void parse_text(buffer *buf)
67 {
68     buffer_skip(buf, 28); // header
69     for (int i = 0; i < MAX_TEXT_ENTRIES; i++) {
70         data.text_entries[i].offset = buffer_read_i32(buf);
71         data.text_entries[i].in_use = buffer_read_i32(buf);
72     }
73     buffer_read_raw(buf, data.text_data, MAX_TEXT_DATA);
74 }
75 
load_text(const char * filename,int localizable,uint8_t * buf_data)76 static int load_text(const char *filename, int localizable, uint8_t *buf_data)
77 {
78     buffer buf;
79     int filesize = io_read_file_into_buffer(filename, localizable, buf_data, BUFFER_SIZE);
80     if (filesize < MIN_TEXT_SIZE || filesize > MAX_TEXT_SIZE) {
81         return 0;
82     }
83     buffer_init(&buf, buf_data, filesize);
84     parse_text(&buf);
85     return 1;
86 }
87 
get_message_text(int32_t offset)88 static uint8_t *get_message_text(int32_t offset)
89 {
90     if (!offset) {
91         return 0;
92     }
93     return &data.message_data[offset];
94 }
95 
parse_message(buffer * buf)96 static void parse_message(buffer *buf)
97 {
98     buffer_skip(buf, 24); // header
99     for (int i = 0; i < MAX_MESSAGE_ENTRIES; i++) {
100         lang_message *m = &data.message_entries[i];
101         m->type = buffer_read_i16(buf);
102         m->message_type = buffer_read_i16(buf);
103         buffer_skip(buf, 2);
104         m->x = buffer_read_i16(buf);
105         m->y = buffer_read_i16(buf);
106         m->width_blocks = buffer_read_i16(buf);
107         m->height_blocks = buffer_read_i16(buf);
108         m->image.id = buffer_read_i16(buf);
109         m->image.x = buffer_read_i16(buf);
110         m->image.y = buffer_read_i16(buf);
111         buffer_skip(buf, 6); // unused image2 id, x, y
112         m->title.x = buffer_read_i16(buf);
113         m->title.y = buffer_read_i16(buf);
114         m->subtitle.x = buffer_read_i16(buf);
115         m->subtitle.y = buffer_read_i16(buf);
116         buffer_skip(buf, 4);
117         m->video.x = buffer_read_i16(buf);
118         m->video.y = buffer_read_i16(buf);
119         buffer_skip(buf, 14);
120         m->urgent = buffer_read_i32(buf);
121 
122         m->video.text = get_message_text(buffer_read_i32(buf));
123         buffer_skip(buf, 4);
124         m->title.text = get_message_text(buffer_read_i32(buf));
125         m->subtitle.text = get_message_text(buffer_read_i32(buf));
126         m->content.text = get_message_text(buffer_read_i32(buf));
127     }
128     buffer_read_raw(buf, &data.message_data, MAX_MESSAGE_DATA);
129 }
130 
131 
set_message_parameters(lang_message * m,int title,int text,int urgent,int message_type)132 static void set_message_parameters(lang_message *m, int title, int text, int urgent, int message_type)
133 {
134     m->type = TYPE_MESSAGE;
135     m->message_type = message_type;
136     m->x = 0;
137     m->y = 0;
138     m->width_blocks = 30;
139     m->height_blocks = 20;
140     m->title.x = 0;
141     m->title.y = 0;
142     m->urgent = urgent;
143 
144     m->title.text = translation_for(title);
145     m->content.text = translation_for(text);
146 }
147 
148 
load_custom_messages(void)149 void load_custom_messages(void)
150 {
151     int i = 321;
152     while (i < MAX_MESSAGE_ENTRIES) {
153         if (!data.message_entries[i].content.text) {
154             break;
155         }
156         i++;
157     }
158 
159     if (i >= MAX_MESSAGE_ENTRIES) {
160         log_error("Message entry max exceeded", "", 0);
161         return;
162     }
163 
164     // soldiers starving
165     lang_message *m = &data.message_entries[i];
166     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_MESS_HALL_NEEDS_FOOD, TR_CITY_MESSAGE_TEXT_MESS_HALL_NEEDS_FOOD, 1,
167         MESSAGE_TYPE_GENERAL);
168     m->video.text = (uint8_t *) "smk/god_mars.smk";
169     i += 1;
170 
171     // soldiers starving, no mess hall
172     m = &data.message_entries[i];
173     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_MESS_HALL_NEEDS_FOOD, TR_CITY_MESSAGE_TEXT_MESS_HALL_MISSING, 1,
174         MESSAGE_TYPE_GENERAL);
175     i += 1;
176 
177     // monument completed
178     m = &data.message_entries[i];
179     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_GRAND_TEMPLE_COMPLETE, TR_CITY_MESSAGE_TEXT_GRAND_TEMPLE_COMPLETE, 0,
180         MESSAGE_TYPE_BUILDING_COMPLETION);
181     i += 1;
182 
183     // replacement Mercury blessing
184     m = &data.message_entries[i];
185     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_MERCURY_BLESSING, TR_CITY_MESSAGE_TEXT_MERCURY_BLESSING, 0,
186         MESSAGE_TYPE_GENERAL);
187     i += 1;
188 
189     // auto festivals
190     m = &data.message_entries[i];
191     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_PANTHEON_FESTIVAL, TR_CITY_MESSAGE_TEXT_PANTHEON_FESTIVAL_CERES, 0,
192         MESSAGE_TYPE_GENERAL);
193     i += 1;
194 
195     m = &data.message_entries[i];
196     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_PANTHEON_FESTIVAL, TR_CITY_MESSAGE_TEXT_PANTHEON_FESTIVAL_NEPTUNE, 0,
197         MESSAGE_TYPE_GENERAL);
198     i += 1;
199 
200     m = &data.message_entries[i];
201     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_PANTHEON_FESTIVAL, TR_CITY_MESSAGE_TEXT_PANTHEON_FESTIVAL_MERCURY, 0,
202         MESSAGE_TYPE_GENERAL);
203     i += 1;
204 
205     m = &data.message_entries[i];
206     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_PANTHEON_FESTIVAL, TR_CITY_MESSAGE_TEXT_PANTHEON_FESTIVAL_MARS, 0,
207         MESSAGE_TYPE_GENERAL);
208     i += 1;
209 
210     m = &data.message_entries[i];
211     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_PANTHEON_FESTIVAL, TR_CITY_MESSAGE_TEXT_PANTHEON_FESTIVAL_VENUS, 0,
212         MESSAGE_TYPE_GENERAL);
213     i += 1;
214 
215     m = &data.message_entries[i];
216     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_MONUMENT_COMPLETE, TR_CITY_MESSAGE_TEXT_PANTHEON_COMPLETE, 0,
217         MESSAGE_TYPE_GENERAL);
218     i += 1;
219 
220     m = &data.message_entries[i];
221     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_MONUMENT_COMPLETE, TR_CITY_MESSAGE_TEXT_LIGHTHOUSE_COMPLETE, 0,
222         MESSAGE_TYPE_GENERAL);
223     i += 1;
224 
225     m = &data.message_entries[i];
226     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_NEPTUNE_BLESSING, TR_CITY_MESSAGE_TEXT_NEPTUNE_BLESSING, 0,
227         MESSAGE_TYPE_GENERAL);
228     i += 1;
229 
230     m = &data.message_entries[i];
231     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_VENUS_BLESSING, TR_CITY_MESSAGE_TEXT_VENUS_BLESSING, 0,
232         MESSAGE_TYPE_GENERAL);
233     i += 1;
234 
235     m = &data.message_entries[i];
236     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_MONUMENT_COMPLETE, TR_CITY_MESSAGE_TEXT_COLOSSEUM_COMPLETE, 0,
237         MESSAGE_TYPE_GENERAL);
238     i += 1;
239 
240     m = &data.message_entries[i];
241     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_MONUMENT_COMPLETE, TR_CITY_MESSAGE_TEXT_HIPPODROME_COMPLETE, 0,
242         MESSAGE_TYPE_GENERAL);
243     i += 1;
244 
245     m = &data.message_entries[i];
246     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_COLOSSEUM_WORKING, TR_CITY_MESSAGE_TEXT_COLOSSEUM_WORKING, 1,
247         MESSAGE_TYPE_GENERAL);
248     m->video.text = (uint8_t *) "smk/1ST_GLAD.smk";
249     i += 1;
250 
251     m = &data.message_entries[i];
252     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_HIPPODROME_WORKING, TR_CITY_MESSAGE_TEXT_HIPPODROME_WORKING, 1,
253         MESSAGE_TYPE_GENERAL);
254     m->video.text = (uint8_t *) "smk/1st_Chariot.smk";
255     i += 1;
256 
257     for (int j = 0; j < 12; ++j) {
258         m = &data.message_entries[i];
259         set_message_parameters(m, TR_CITY_MESSAGE_TITLE_GREAT_GAMES, TR_CITY_MESSAGE_TEXT_NAVAL_GAMES_PLANNING + j, 1,
260             MESSAGE_TYPE_GENERAL);
261         i += 1;
262     }
263 
264     m = &data.message_entries[i];
265     set_message_parameters(m, TR_CITY_MESSAGE_TITLE_LOOTING, TR_CITY_MESSAGE_TEXT_LOOTING, 1, MESSAGE_TYPE_DISASTER);
266     i += 1;
267 
268     for (int j = 0; j < 3; ++j) {
269         m = &data.message_entries[i];
270         set_message_parameters(m, TR_CITY_MESSAGE_TITLE_GREAT_GAMES, TR_CITY_MESSAGE_TEXT_IMPERIAL_GAMES_PLANNING + j, 1,
271             MESSAGE_TYPE_GENERAL);
272         i += 1;
273     }
274 }
275 
276 
load_message(const char * filename,int localizable,uint8_t * data_buffer)277 static int load_message(const char *filename, int localizable, uint8_t *data_buffer)
278 {
279     buffer buf;
280     int filesize = io_read_file_into_buffer(filename, localizable, data_buffer, BUFFER_SIZE);
281     if (filesize < MIN_MESSAGE_SIZE || filesize > MAX_MESSAGE_SIZE) {
282         return 0;
283     }
284     buffer_init(&buf, data_buffer, filesize);
285     parse_message(&buf);
286     return 1;
287 }
288 
load_files(const char * text_filename,const char * message_filename,int localizable)289 static int load_files(const char *text_filename, const char *message_filename, int localizable)
290 {
291     uint8_t *buffer = (uint8_t *) malloc(BUFFER_SIZE);
292     if (!buffer) {
293         return 0;
294     }
295     int success = load_text(text_filename, localizable, buffer) && load_message(message_filename, localizable, buffer);
296     free(buffer);
297     return success;
298 }
299 
lang_load(int is_editor)300 int lang_load(int is_editor)
301 {
302     if (is_editor) {
303         return load_files(FILE_EDITOR_TEXT_ENG, FILE_EDITOR_MM_ENG, MAY_BE_LOCALIZED);
304     }
305     // Prefer language files from localized dir, fall back to main dir
306     return
307         load_files(FILE_TEXT_ENG, FILE_MM_ENG, MUST_BE_LOCALIZED) ||
308         load_files(FILE_TEXT_RUS, FILE_MM_RUS, MUST_BE_LOCALIZED) ||
309         load_files(FILE_TEXT_ENG, FILE_MM_ENG, NOT_LOCALIZED) ||
310         load_files(FILE_TEXT_RUS, FILE_MM_RUS, NOT_LOCALIZED);
311 }
312 
lang_get_string(int group,int index)313 const uint8_t *lang_get_string(int group, int index)
314 {
315     if (group == CUSTOM_TRANSLATION) {
316         return translation_for(index);
317     }
318     if (group == 92 && !index) {
319         return translation_for(TR_BUILDING_SMALL_TEMPLE_CERES_NAME);
320     }
321     if (group == 93 && !index) {
322         return translation_for(TR_BUILDING_SMALL_TEMPLE_NEPTUNE_NAME);
323     }
324     if (group == 94 && !index) {
325         return translation_for(TR_BUILDING_SMALL_TEMPLE_MERCURY_NAME);
326     }
327     if (group == 95 && !index) {
328         return translation_for(TR_BUILDING_SMALL_TEMPLE_MARS_NAME);
329     }
330     if (group == 96 && !index) {
331         return translation_for(TR_BUILDING_SMALL_TEMPLE_VENUS_NAME);
332     }
333     if (group == 23 && index == 6 && scenario_building_allowed(BUILDING_WHARF)) {
334         return translation_for(TR_RESOURCE_FISH);
335     }
336 
337     if (group == 130) {
338         switch (index) {
339             case 641:
340                 return translation_for(TR_PHRASE_FIGURE_MISSIONARY_EXACT_4);
341             default:
342                 break;
343         }
344     }
345 
346     if (group == 67 && index == 48) {
347         return translation_for(TR_EDITOR_ALLOWED_BUILDINGS_MONUMENTS);
348     }
349 
350     // Building strings
351     if (group == 28 || group == 41) {
352         switch (index) {
353             case BUILDING_ROADBLOCK:
354                 return translation_for(TR_BUILDING_ROADBLOCK);
355             case BUILDING_WORKCAMP:
356                 return translation_for(TR_BUILDING_WORK_CAMP);
357             case BUILDING_GRAND_TEMPLE_CERES:
358                 return translation_for(TR_BUILDING_GRAND_TEMPLE_CERES);
359             case BUILDING_GRAND_TEMPLE_NEPTUNE:
360                 return translation_for(TR_BUILDING_GRAND_TEMPLE_NEPTUNE);
361             case BUILDING_GRAND_TEMPLE_MERCURY:
362                 return translation_for(TR_BUILDING_GRAND_TEMPLE_MERCURY);
363             case BUILDING_GRAND_TEMPLE_MARS:
364                 return translation_for(TR_BUILDING_GRAND_TEMPLE_MARS);
365             case BUILDING_GRAND_TEMPLE_VENUS:
366                 return translation_for(TR_BUILDING_GRAND_TEMPLE_VENUS);
367             case BUILDING_PANTHEON:
368                 return translation_for(TR_BUILDING_PANTHEON);
369             case BUILDING_MENU_GRAND_TEMPLES:
370                 return translation_for(TR_BUILDING_GRAND_TEMPLE_MENU);
371             case BUILDING_ARCHITECT_GUILD:
372                 return translation_for(TR_BUILDING_ARCHITECT_GUILD);
373             case BUILDING_MESS_HALL:
374                 return translation_for(TR_BUILDING_MESS_HALL);
375             case BUILDING_MENU_TREES:
376                 return translation_for(TR_BUILDING_MENU_TREES);
377             case BUILDING_MENU_PATHS:
378                 return translation_for(TR_BUILDING_MENU_PATHS);
379             case BUILDING_MENU_PARKS:
380                 return translation_for(TR_BUILDING_MENU_PARKS);
381             case BUILDING_SMALL_POND:
382                 return translation_for(TR_BUILDING_SMALL_POND);
383             case BUILDING_LARGE_POND:
384                 return translation_for(TR_BUILDING_LARGE_POND);
385             case BUILDING_PINE_TREE:
386                 return translation_for(TR_BUILDING_PINE_TREE);
387             case BUILDING_FIR_TREE:
388                 return translation_for(TR_BUILDING_FIR_TREE);
389             case BUILDING_OAK_TREE:
390                 return translation_for(TR_BUILDING_OAK_TREE);
391             case BUILDING_ELM_TREE:
392                 return translation_for(TR_BUILDING_ELM_TREE);
393             case BUILDING_FIG_TREE:
394                 return translation_for(TR_BUILDING_FIG_TREE);
395             case BUILDING_PLUM_TREE:
396                 return translation_for(TR_BUILDING_PLUM_TREE);
397             case BUILDING_PALM_TREE:
398                 return translation_for(TR_BUILDING_PALM_TREE);
399             case BUILDING_DATE_TREE:
400                 return translation_for(TR_BUILDING_DATE_TREE);
401             case BUILDING_PINE_PATH:
402                 return translation_for(TR_BUILDING_PINE_PATH);
403             case BUILDING_FIR_PATH:
404                 return translation_for(TR_BUILDING_FIR_PATH);
405             case BUILDING_OAK_PATH:
406                 return translation_for(TR_BUILDING_OAK_PATH);
407             case BUILDING_ELM_PATH:
408                 return translation_for(TR_BUILDING_ELM_PATH);
409             case BUILDING_FIG_PATH:
410                 return translation_for(TR_BUILDING_FIG_PATH);
411             case BUILDING_PLUM_PATH:
412                 return translation_for(TR_BUILDING_PLUM_PATH);
413             case BUILDING_PALM_PATH:
414                 return translation_for(TR_BUILDING_PALM_PATH);
415             case BUILDING_DATE_PATH:
416                 return translation_for(TR_BUILDING_DATE_PATH);
417             case BUILDING_PAVILION_BLUE:
418                 return translation_for(TR_BUILDING_BLUE_PAVILION);
419             case BUILDING_PAVILION_RED:
420                 return translation_for(TR_BUILDING_RED_PAVILION);
421             case BUILDING_PAVILION_ORANGE:
422                 return translation_for(TR_BUILDING_ORANGE_PAVILION);
423             case BUILDING_PAVILION_YELLOW:
424                 return translation_for(TR_BUILDING_YELLOW_PAVILION);
425             case BUILDING_PAVILION_GREEN:
426                 return translation_for(TR_BUILDING_GREEN_PAVILION);
427             case BUILDING_SMALL_STATUE_ALT:
428                 return translation_for(TR_BUILDING_SMALL_STATUE_ALT);
429             case BUILDING_SMALL_STATUE_ALT_B:
430                 return translation_for(TR_BUILDING_SMALL_STATUE_ALT_B);
431             case BUILDING_OBELISK:
432                 return translation_for(TR_BUILDING_OBELISK);
433             case BUILDING_LIGHTHOUSE:
434                 return translation_for(TR_BUILDING_LIGHTHOUSE);
435             case BUILDING_MENU_GOV_RES:
436                 return translation_for(TR_BUILDING_MENU_GOV_RES);
437             case BUILDING_MENU_STATUES:
438                 return translation_for(TR_BUILDING_MENU_STATUES);
439             case BUILDING_TAVERN:
440                 return translation_for(TR_BUILDING_TAVERN);
441             case BUILDING_GRAND_GARDEN:
442                 return translation_for(TR_BUILDING_GRAND_GARDEN);
443             case BUILDING_ARENA:
444                 return translation_for(TR_BUILDING_ARENA);
445             case BUILDING_HORSE_STATUE:
446                 return translation_for(TR_BUILDING_HORSE_STATUE);
447             case BUILDING_DOLPHIN_FOUNTAIN:
448                 return translation_for(TR_BUILDING_DOLPHIN_FOUNTAIN);
449             case BUILDING_HEDGE_DARK:
450                 return translation_for(TR_BUILDING_HEDGE_DARK);
451             case BUILDING_HEDGE_LIGHT:
452                 return translation_for(TR_BUILDING_HEDGE_LIGHT);
453             case BUILDING_GARDEN_WALL:
454                 return translation_for(TR_BUILDING_GARDEN_WALL);
455             case BUILDING_LEGION_STATUE:
456                 return translation_for(TR_BUILDING_LEGION_STATUE);
457             case BUILDING_DECORATIVE_COLUMN:
458                 return translation_for(TR_BUILDING_DECORATIVE_COLUMN);
459             case BUILDING_COLONNADE:
460                 return translation_for(TR_BUILDING_COLONNADE);
461             case BUILDING_GARDEN_PATH:
462                 return translation_for(TR_BUILDING_GARDEN_PATH);
463             case BUILDING_LARARIUM:
464                 return translation_for(TR_BUILDING_LARARIUM);
465             case BUILDING_NYMPHAEUM:
466                 return translation_for(TR_BUILDING_NYMPHAEUM);
467             case BUILDING_WATCHTOWER:
468                 return translation_for(TR_BUILDING_WATCHTOWER);
469             case BUILDING_SMALL_MAUSOLEUM:
470                 return translation_for(TR_BUILDING_SMALL_MAUSOLEUM);
471             case BUILDING_LARGE_MAUSOLEUM:
472                 return translation_for(TR_BUILDING_LARGE_MAUSOLEUM);
473             case BUILDING_CARAVANSERAI:
474                 return translation_for(TR_BUILDING_CARAVANSERAI);
475             case BUILDING_ROOFED_GARDEN_WALL:
476                 return translation_for(TR_BUILDING_ROOFED_GARDEN_WALL);
477             case BUILDING_GARDEN_WALL_GATE:
478                 return translation_for(TR_BUILDING_GARDEN_WALL_GATE);
479             case BUILDING_PALISADE:
480                 return translation_for(TR_BUILDING_PALISADE);
481             default:
482                 break;
483         }
484     }
485 
486     const uint8_t *str = &data.text_data[data.text_entries[group].offset];
487     uint8_t prev = 0;
488     while (index > 0) {
489         if (!*str && (prev >= ' ' || prev == 0)) {
490             --index;
491         }
492         prev = *str;
493         ++str;
494     }
495     while (*str < ' ') { // skip non-printables
496         ++str;
497     }
498     return str;
499 }
500 
lang_get_message(int id)501 const lang_message *lang_get_message(int id)
502 {
503     return &data.message_entries[id];
504 }
505