OSDN Git Service

Merge branch 'develop' into macos-develop
[hengbandforosx/hengbandosx.git] / src / system / grafmode.cpp
1 /**
2  * \file grafmode.cpp
3  * \brief Load a list of possible graphics modes.
4  *
5  * Copyright (c) 2011 Brett Reid
6  *
7  * This work is free software; you can redistribute it and/or modify it
8  * under the terms of either:
9  *
10  * a) the GNU General Public License as published by the Free Software
11  *    Foundation, version 2, or
12  *
13  * b) the "Angband license":
14  *    This software may be copied and distributed for educational, research,
15  *    and not for profit purposes provided that this copyright and statement
16  *    are included in all such copies.  Other copyrights may also apply.
17  */
18 /*
19  * Imported from Angband 4.2.0 to support main-cocoa.mm for Hengband.  Did not
20  * bring over the datafile parsing (datafile.h and datafile.c) so the
21  * implementation here is different from that in Angband.
22  */
23 #include "system/angband.h"
24 #include "system/grafmode.h"
25 #include "io/files-util.h"
26 #include "util/angband-files.h"
27 #include "util/string-processor.h"
28 #include "view/display-messages.h"
29
30 #define GFPARSE_HAVE_NOTHING (0)
31 #define GFPARSE_HAVE_NAME (1)
32 #define GFPARSE_HAVE_DIR (2)
33 #define GFPARSE_HAVE_SIZE (4)
34 #define GFPARSE_HAVE_PREF (8)
35 #define GFPARSE_HAVE_EXTRA (16)
36 #define GFPARSE_HAVE_GRAF (32)
37
38 typedef struct GrafModeParserState {
39     graphics_mode* list;
40     char* file_name;
41     int dir_len;
42     int line_no;
43     int stage;
44     errr result;
45 } GrafModeParserState;
46
47
48 /* This is the global state exposed to Angband. */
49 graphics_mode *graphics_modes;
50 graphics_mode *current_graphics_mode = NULL;
51 int graphics_mode_high_id;
52
53
54 static int check_last_mode(GrafModeParserState* pgps) {
55     int result = 0;
56
57     if ((pgps->stage & GFPARSE_HAVE_DIR) == 0) {
58         result = 1;
59         msg_format("no directory set for tile set, %s, in %s",
60                    pgps->list->menuname, pgps->file_name);
61     }
62     if ((pgps->stage & GFPARSE_HAVE_SIZE) == 0) {
63         result = 1;
64         msg_format("no size set for tile set, %s, in %s",
65                    pgps->list->menuname, pgps->file_name);
66     }
67     if ((pgps->stage & GFPARSE_HAVE_PREF) == 0) {
68         result = 1;
69         msg_format("no preference file for tile set, %s, in %s",
70                    pgps->list->menuname, pgps->file_name);
71     }
72     if ((pgps->stage & GFPARSE_HAVE_GRAF) == 0) {
73         result = 1;
74         msg_format("no graf string set for tile set, %s, in %s",
75                    pgps->list->menuname, pgps->file_name);
76     }
77     return result;
78 }
79
80
81 static void parse_line(GrafModeParserState* pgps, const char* line) {
82     static const char whitespc[] = " \t\v\f";
83     int offset = 0;
84     int stage;
85
86     while (line[offset] && strchr(whitespc, line[offset]) != 0) {
87         ++offset;
88     }
89     if (! line[offset] || line[offset] == '#') {
90         return;
91     }
92
93     if (strncmp(line + offset, "name:", 5) == 0) {
94         stage = GFPARSE_HAVE_NAME;
95         offset += 5;
96     } else if (strncmp(line + offset, "directory:", 10) == 0) {
97         stage = GFPARSE_HAVE_DIR;
98         offset += 10;
99     } else if (strncmp(line + offset, "size:", 5) == 0) {
100         stage = GFPARSE_HAVE_SIZE;
101         offset += 5;
102     } else if (strncmp(line + offset, "pref:", 5) == 0) {
103         stage = GFPARSE_HAVE_PREF;
104         offset += 5;
105     } else if (strncmp(line + offset, "extra:", 6) == 0) {
106         stage = GFPARSE_HAVE_EXTRA;
107         offset += 6;
108     } else if (strncmp(line + offset, "graf:", 5) == 0) {
109         stage = GFPARSE_HAVE_GRAF;
110         offset += 5;
111     } else {
112         msg_format("Unexpected data at line %d of %s", pgps->line_no,
113                    pgps->file_name);
114         pgps->result = 1;
115         return;
116     }
117
118     if (stage == GFPARSE_HAVE_NAME) {
119         graphics_mode *new_mode;
120
121         if (pgps->stage != GFPARSE_HAVE_NOTHING) {
122             if (check_last_mode(pgps) != 0) {
123                 pgps->result = 1;
124             }
125         }
126
127         pgps->stage = GFPARSE_HAVE_NAME;
128         new_mode = (graphics_mode *) malloc(sizeof(graphics_mode));
129         if (new_mode == 0) {
130             pgps->result = 1;
131             msg_format("failed memory allocation for tile set "
132                        "information at line %d of %s", pgps->line_no,
133                        pgps->file_name);
134             return;
135         }
136         new_mode->pNext = pgps->list;
137         new_mode->grafID = 0;
138         new_mode->alphablend = 0;
139         new_mode->overdrawRow = 0;
140         new_mode->overdrawMax = 0;
141         new_mode->cell_width = 0;
142         new_mode->cell_height = 0;
143         new_mode->path[0] = '\0';
144         new_mode->pref[0] = '\0';
145         new_mode->file[0] = '\0';
146         new_mode->menuname[0] = '\0';
147         new_mode->graf[0] = '\0';
148         pgps->list = new_mode;
149     } else {
150         if (pgps->stage == GFPARSE_HAVE_NOTHING) {
151             pgps->result = 1;
152             msg_format("values set before tile set name given at line %d"
153                        " of %s", pgps->line_no, pgps->file_name);
154             return;
155         }
156         if (pgps->stage & stage) {
157             msg_format("values set more than once for tile set, %s, at line "
158                        " %d of %s", pgps->list->menuname, pgps->line_no,
159                        pgps->file_name);
160         }
161     }
162
163     switch (stage) {
164     case GFPARSE_HAVE_NAME:
165         {
166             unsigned int id;
167             int nscan;
168
169             if (sscanf(line + offset, "%u:%n", &id, &nscan) == 1) {
170                 if (id > 255) {
171                     pgps->result = 1;
172                     msg_format("ID greater than 255 for tile set at line"
173                                " %d of %s", pgps->line_no, pgps->file_name);
174                 } else if (id == GRAPHICS_NONE) {
175                     pgps->result = 1;
176                     msg_format("ID of tile set matches value, %d, reserved "
177                                " for no graphics at line %d of %s",
178                                GRAPHICS_NONE, pgps->line_no, pgps->file_name);
179                 } else {
180                     graphics_mode *mode = pgps->list->pNext;
181
182                     while (1) {
183                         if (mode == 0) {
184                             break;
185                         }
186                         if (mode->grafID == id) {
187                             pgps->result = 1;
188                             msg_format("ID for tile set, %s, at line %d of %s"
189                                        " is the same as for tile set %s",
190                                        pgps->list->menuname, pgps->line_no,
191                                        pgps->file_name, mode->menuname);
192                             break;
193                         }
194                         mode = mode->pNext;
195                     }
196                     pgps->list->grafID = id;
197                 }
198                 offset += nscan;
199                 if (strlen(line + offset) >= sizeof(pgps->list->menuname)) {
200                     pgps->result = 1;
201                     msg_format("name is too long for tile set at line %d"
202                                " of %s", pgps->line_no, pgps->file_name);
203                 } else if (line[offset] == '\0') {
204                     pgps->result = 1;
205                     msg_format("empty name for tile set at line %d of %s",
206                                pgps->line_no, pgps->file_name);
207                 } else {
208                     strcpy(pgps->list->menuname, line + offset);
209                 }
210             } else {
211                 pgps->result = 1;
212                 msg_format("malformed ID for tile set at line %d of %s",
213                            pgps->line_no, pgps->file_name);
214             }
215         }
216         break;
217
218     case GFPARSE_HAVE_DIR:
219         {
220             size_t len = strlen(line + offset);
221             size_t sep_len = strlen(PATH_SEP);
222
223             if (len >= sizeof(pgps->list->path) ||
224                 len + pgps->dir_len + sep_len >= sizeof(pgps->list->path)) {
225                 pgps->result = 1;
226                 msg_format("directory name is too long for tile set, %s, at"
227                            " line %d of %s", pgps->list->menuname,
228                            pgps->line_no, pgps->file_name);
229             } else if (line[offset] == '\0') {
230                 pgps->result = 1;
231                 msg_format("empty directory name for tile set, %s, at line"
232                            " line %d of %s", pgps->list->menuname,
233                            pgps->line_no, pgps->file_name);
234             } else {
235                 /*
236                  * Temporarily hack the path to list.txt so it is not necessary
237                  * to separately store the base directory for the tile files.
238                  */
239                 char chold = pgps->file_name[pgps->dir_len];
240                 std::filesystem::path p;
241
242                 pgps->file_name[pgps->dir_len] = '\0';
243                 p = path_build(std::filesystem::path(pgps->file_name,
244                         std::filesystem::path::native_format), line + offset);
245                 pgps->file_name[pgps->dir_len] = chold;
246                 angband_strcpy(pgps->list->path, p.native().data(),
247                         sizeof(pgps->list->path));
248             }
249         }
250         break;
251
252     case GFPARSE_HAVE_SIZE:
253         {
254             unsigned w, h;
255             int nscan;
256
257             if (sscanf(line + offset, "%u:%u:%n", &w, &h, &nscan) == 2) {
258                 if (w > 0) {
259                     pgps->list->cell_width = w;
260                 } else {
261                     pgps->result = 1;
262                     msg_format("zero width for tile set, %s, at line"
263                                " %d of %s", pgps->list->menuname,
264                                pgps->line_no, pgps->file_name);
265                 }
266                 if (h > 0) {
267                     pgps->list->cell_height = h;
268                 } else {
269                     pgps->result = 1;
270                     msg_format("zero height for tile set, %s, at line"
271                                " %d of %s", pgps->list->menuname,
272                                pgps->line_no, pgps->file_name);
273                 }
274                 offset += nscan;
275                 if (strlen(line + offset) >= sizeof(pgps->list->file)) {
276                     pgps->result = 1;
277                     msg_format("file name is too long for tile set, %s,"
278                                " at line %d of %s", pgps->list->menuname,
279                                pgps->line_no, pgps->file_name);
280                 } else if (line[offset] == '\0') {
281                     pgps->result = 1;
282                     msg_format("empty file name for tile set, %s, at line %d"
283                                " of %s", pgps->list->menuname, pgps->line_no,
284                                pgps->file_name);
285                 } else {
286                     (void) strcpy(pgps->list->file, line + offset);
287                 }
288             } else {
289                 pgps->result = 1;
290                 msg_format("malformed dimensions for tile set, %s, at line"
291                            " %d of %s", pgps->list->menuname, pgps->line_no,
292                            pgps->file_name);
293             }
294         }
295         break;
296
297     case GFPARSE_HAVE_PREF:
298         if (strlen(line + offset) >= sizeof(pgps->list->pref)) {
299             pgps->result = 1;
300             msg_format("preference file name is too long for tile set, %s, "
301                        "at line %d of %s", pgps->list->menuname, pgps->line_no,
302                        pgps->file_name);
303         } else if (line[offset] == '\0') {
304             pgps->result = 1;
305             msg_format("empty preference file name for tile set, %s, "
306                        "at line %d of %s", pgps->list->menuname, pgps->line_no,
307                        pgps->file_name);
308         } else {
309             strcpy(pgps->list->pref, line + offset);
310         }
311         break;
312
313     case GFPARSE_HAVE_EXTRA:
314         {
315             unsigned int alpha, startdbl, enddbl;
316             int nscan;
317
318             if (sscanf(line + offset, "%u:%u:%u%n",
319                        &alpha, &startdbl, &enddbl, &nscan) == 3 &&
320                 (line[offset + nscan] == '\0' ||
321                  strchr(whitespc, line[offset + nscan]) != 0 ||
322                  line[offset + nscan] == '#')) {
323                 if (startdbl > 255 || enddbl > 255) {
324                     pgps->result = 1;
325                     msg_format("overdrawMax or overdrawRow is greater than"
326                                " 255 for tile set, %s, at line %d of %s",
327                                pgps->list->menuname, pgps->line_no,
328                                pgps->file_name);
329                 } else if (enddbl < startdbl) {
330                     pgps->result = 1;
331                     msg_format("overdrawMax less than overdrawRow for tile"
332                                "set, %s, at line %d of %s",
333                                pgps->list->menuname, pgps->line_no,
334                                pgps->file_name);
335                 } else {
336                     pgps->list->alphablend = (alpha != 0);
337                     pgps->list->overdrawRow = startdbl;
338                     pgps->list->overdrawMax = enddbl;
339                 }
340             } else {
341                 pgps->result = 1;
342                 msg_format("malformed data for tile set, %s, at line %d of"
343                            " %s", pgps->list->menuname, pgps->line_no,
344                            pgps->file_name);
345             }
346         }
347         break;
348
349     case GFPARSE_HAVE_GRAF:
350         if (strlen(line + offset) >= sizeof(pgps->list->graf)) {
351             pgps->result = 1;
352             msg_format("graf string is too long for tile set, %s, at line %d"
353                        " of %s", pgps->list->menuname, pgps->line_no,
354                        pgps->file_name);
355         } else if (line[offset] == '\0') {
356             pgps->result = 1;
357             msg_format("empty graf string for tile set, %s, at line %d of %s",
358                        pgps->list->menuname, pgps->line_no, pgps->file_name);
359         } else {
360             strcpy(pgps->list->graf, line + offset);
361         }
362         break;
363     }
364
365     if (pgps->result == 0) {
366         pgps->stage |= stage;
367     }
368 }
369
370
371 static void finish_parse_grafmode(GrafModeParserState* pgps,
372                                   int transfer_results) {
373     /*
374      * Check what was read for the last mode parsed, since parse_line did
375      * not.
376      */
377     if (transfer_results) {
378         if (pgps->list == 0 || pgps->stage == GFPARSE_HAVE_NOTHING) {
379             msg_format("no graphics modes in %s", pgps->file_name);
380         } else {
381             if (check_last_mode(pgps) != 0) {
382                 transfer_results = 0;
383                 pgps->result = 1;
384             }
385         }
386     }
387
388     if (transfer_results) {
389         graphics_mode *mode = pgps->list;
390         int max = GRAPHICS_NONE;
391         int count = 0;
392         graphics_mode *new_list;
393
394         while (mode) {
395             if (mode->grafID > max) {
396                 max = mode->grafID;
397             }
398             ++count;
399             mode = mode->pNext;
400         }
401
402         /* Assemble the modes into a contiguous block of memory. */
403         new_list = (graphics_mode *)
404             malloc(sizeof(graphics_mode) * (count + 1));
405         if (new_list != 0) {
406             int i;
407
408             mode = pgps->list;
409             for (i = count - 1; i >= 0; --i, mode = mode->pNext) {
410                 memcpy(&(new_list[i]), mode, sizeof(graphics_mode));
411                 new_list[i].pNext = &(new_list[i + 1]);
412             }
413
414             /* Hardcode the no graphics option. */
415             new_list[count].pNext = NULL;
416             new_list[count].grafID = GRAPHICS_NONE;
417             new_list[count].alphablend = 0;
418             new_list[count].overdrawRow = 0;
419             new_list[count].overdrawMax = 0;
420             strncpy(
421                 new_list[count].pref, "none", sizeof(new_list[count].pref));
422             strncpy(
423                 new_list[count].path, "", sizeof(new_list[count].path));
424             strncpy(
425                 new_list[count].file, "", sizeof(new_list[count].file));
426             strncpy(
427                 new_list[count].menuname,
428                 "Classic ASCII",
429                 sizeof(new_list[count].menuname)
430             );
431             strncpy(
432                 new_list[count].graf, "ascii", sizeof(new_list[count].graf));
433
434             /* Release the old global state. */
435             close_graphics_modes();
436
437             graphics_modes = new_list;
438             graphics_mode_high_id = max;
439             /* Set the default graphics mode to be no graphics */
440             current_graphics_mode = &(graphics_modes[count]);
441         } else {
442             pgps->result = 1;
443             msg_print("failed memory allocation for new graphics modes");
444         }
445     }
446
447     /* Release the memory allocated for parsing the file. */
448     while (pgps->list != 0) {
449         graphics_mode *mode = pgps->list;
450
451         pgps->list = mode->pNext;
452         free(mode);
453     }
454 }
455
456
457 bool init_graphics_modes(void) {
458     char buf[1024];
459     char line[1024];
460     GrafModeParserState gps = { 0, buf, 0, 0, GFPARSE_HAVE_NOTHING, 0 };
461     std::filesystem::path p;
462     FILE *f;
463
464     /* Build the filename */
465     p = path_build(ANGBAND_DIR_XTRA, "graf");
466     angband_strcpy(line, p.native().data(), sizeof(line));
467     gps.dir_len = strlen(line);
468     p = path_build(p, "list.txt");
469     angband_strcpy(buf, p.native().data(), sizeof(buf));
470
471     f = angband_fopen(buf, FileOpenMode::READ);
472     if (!f) {
473         msg_format("Cannot open '%s'.", buf);
474         gps.result = 1;
475     } else {
476         while (angband_fgets(f, line, sizeof line) == 0) {
477             ++gps.line_no;
478             parse_line(&gps, line);
479             if (gps.result != 0) {
480                 break;
481             }
482         }
483
484         finish_parse_grafmode(&gps, gps.result == 0);
485         angband_fclose(f);
486     }
487
488     /* Result */
489     return gps.result == 0;
490 }
491
492
493 void close_graphics_modes(void) {
494     if (graphics_modes) {
495         free(graphics_modes);
496         graphics_modes = NULL;
497     }
498     current_graphics_mode = NULL;
499 }
500
501
502 graphics_mode *get_graphics_mode(byte id) {
503     graphics_mode *test = graphics_modes;
504     while (test) {
505         if (test->grafID == id) {
506             return test;
507         }
508         test = test->pNext;
509     }
510     return NULL;
511 }