1 #include "xml.h"
2 
3 #include "assets/group.h"
4 #include "assets/image.h"
5 #include "core/calc.h"
6 #include "core/dir.h"
7 #include "core/file.h"
8 #include "core/log.h"
9 #include "core/png_read.h"
10 #include "core/string.h"
11 
12 #include "expat.h"
13 
14 #include <string.h>
15 
16 #define XML_HASH_SEED 0x12345678
17 #define XML_BUFFER_SIZE 1024
18 #define XML_MAX_DEPTH 4
19 #define XML_MAX_ELEMENTS_PER_DEPTH 2
20 #define XML_MAX_ATTRIBUTES 8
21 #define XML_TAG_MAX_LENGTH 12
22 
23 static const char XML_FILE_ELEMENTS[XML_MAX_DEPTH][XML_MAX_ELEMENTS_PER_DEPTH][XML_TAG_MAX_LENGTH] = { { "assetlist" }, { "image" }, { "layer", "animation" }, { "frame" } };
24 static const char XML_FILE_ATTRIBUTES[XML_MAX_DEPTH][XML_MAX_ELEMENTS_PER_DEPTH][XML_MAX_ATTRIBUTES][XML_TAG_MAX_LENGTH] = {
25     { { "name" } }, // assetlist
26     { { "id", "src", "width", "height", "group", "image" } }, // image
27     { { "src", "group", "image", "x", "y", "invert", "rotate", "part" }, // layer
28     { "frames", "speed", "reversible", "x", "y" } }, // animation
29     { { "src", "width", "height", "group", "image" } } // frame
30 };
31 
32 static void xml_start_assetlist_element(const char **attributes);
33 static void xml_start_image_element(const char **attributes);
34 static void xml_start_layer_element(const char **attributes);
35 static void xml_start_animation_element(const char **attributes);
36 static void xml_start_frame_element(const char **attributes);
37 static void xml_end_assetlist_element(void);
38 static void xml_end_image_element(void);
39 static void xml_end_layer_element(void);
40 static void xml_end_animation_element(void);
41 static void xml_end_frame_element(void);
42 
43 static void (*xml_start_element_callback[XML_MAX_DEPTH][XML_MAX_ELEMENTS_PER_DEPTH])(const char **attributes) = {
44     { xml_start_assetlist_element },
45     { xml_start_image_element },
46     { xml_start_layer_element, xml_start_animation_element },
47     { xml_start_frame_element }
48 };
49 
50 static void (*xml_end_element_callback[XML_MAX_DEPTH][XML_MAX_ELEMENTS_PER_DEPTH])(void) = {
51     { xml_end_assetlist_element },
52     { xml_end_image_element },
53     { xml_end_layer_element, xml_end_animation_element },
54     { xml_end_frame_element }
55 };
56 
57 static struct {
58     char file_name[FILE_NAME_MAX];
59     size_t file_name_position;
60     int depth;
61     int error;
62     int finished;
63     int in_animation;
64     image_groups *current_group;
65     asset_image *current_image;
66 } data;
67 
set_asset_image_base_path(const char * name)68 static void set_asset_image_base_path(const char *name)
69 {
70     size_t position = 0;
71     char *dst = data.file_name;
72     memset(dst, 0, FILE_NAME_MAX);
73     strncpy(dst, name, FILE_NAME_MAX - position - 1);
74     position += strlen(name);
75     dst[position++] = '/';
76     data.file_name_position = position;
77 }
78 
count_xml_attributes(const char ** attributes)79 static int count_xml_attributes(const char **attributes)
80 {
81     int total = 0;
82     while (attributes[total]) {
83         ++total;
84     }
85     return total;
86 }
87 
xml_start_assetlist_element(const char ** attributes)88 static void xml_start_assetlist_element(const char **attributes)
89 {
90     data.current_group = group_get_new();
91     if (count_xml_attributes(attributes) != 2) {
92         data.error = 1;
93         return;
94     }
95     if (strcmp(attributes[0], XML_FILE_ATTRIBUTES[0][0][0]) == 0) {
96         strncpy(data.current_group->name, attributes[1], XML_STRING_MAX_LENGTH - 1);
97     }
98     if (*data.current_group->name == '\0') {
99         data.error = 1;
100         return;
101     }
102     data.current_image = 0;
103     set_asset_image_base_path(data.current_group->name);
104 }
105 
xml_start_image_element(const char ** attributes)106 static void xml_start_image_element(const char **attributes)
107 {
108     int total_attributes = count_xml_attributes(attributes);
109     if (total_attributes < 2 || total_attributes > 10 || total_attributes % 2) {
110         data.error = 1;
111         return;
112     }
113     asset_image *img = asset_image_create();
114     if (!img) {
115         data.error = 1;
116         return;
117     }
118     if (!data.current_image) {
119         data.current_group->first_image_index = img->index;
120     }
121     data.current_group->last_image_index = img->index;
122     data.current_image = img;
123     const char *path = 0;
124     const char *group = 0;
125     const char *id = 0;
126     for (int i = 0; i < total_attributes; i += 2) {
127         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[1][0][0]) == 0) {
128             strncpy(img->id, attributes[i + 1], XML_STRING_MAX_LENGTH - 1);
129         }
130         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[1][0][1]) == 0) {
131             path = attributes[i + 1];
132         }
133         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[1][0][2]) == 0) {
134             img->img.width = string_to_int(string_from_ascii(attributes[i + 1]));
135         }
136         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[1][0][3]) == 0) {
137             img->img.height = string_to_int(string_from_ascii(attributes[i + 1]));
138         }
139         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[1][0][4]) == 0) {
140             group = attributes[i + 1];
141         }
142         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[1][0][5]) == 0) {
143             id = attributes[i + 1];
144         }
145     }
146     img->last_layer = &img->first_layer;
147     if (path || group) {
148         asset_image_add_layer(img, path, group, id, 0, 0, INVERT_NONE, ROTATE_NONE, PART_BOTH);
149     }
150 }
151 
xml_start_layer_element(const char ** attributes)152 static void xml_start_layer_element(const char **attributes)
153 {
154     const char *path = 0;
155     const char *group = 0;
156     const char *id = 0;
157     int offset_x = 0;
158     int offset_y = 0;
159     asset_image *img = data.current_image;
160     int total_attributes = count_xml_attributes(attributes);
161     if (total_attributes < 2 || total_attributes > 14 || total_attributes % 2) {
162         data.error = 1;
163         return;
164     }
165     layer_invert_type invert = INVERT_NONE;
166     layer_rotate_type rotate = ROTATE_NONE;
167     layer_isometric_part part = PART_BOTH;
168     for (int i = 0; i < total_attributes; i += 2) {
169         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][0][0]) == 0) {
170             path = attributes[i + 1];
171         }
172         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][0][1]) == 0) {
173             group = attributes[i + 1];
174         }
175         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][0][2]) == 0) {
176             id = attributes[i + 1];
177         }
178         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][0][3]) == 0) {
179             offset_x = string_to_int(string_from_ascii(attributes[i + 1]));
180         }
181         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][0][4]) == 0) {
182             offset_y = string_to_int(string_from_ascii(attributes[i + 1]));
183         }
184         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][0][5]) == 0) {
185             if (strcmp(attributes[i + 1], "horizontal") == 0) {
186                 invert = INVERT_HORIZONTAL;
187             } else if (strcmp(attributes[i + 1], "vertical") == 0) {
188                 invert = INVERT_VERTICAL;
189             } else if (strcmp(attributes[i + 1], "both") == 0) {
190                 invert = INVERT_BOTH;
191             }
192         }
193         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][0][6]) == 0) {
194             if (strcmp(attributes[i + 1], "90") == 0) {
195                 rotate = ROTATE_90_DEGREES;
196             } else if (strcmp(attributes[i + 1], "180") == 0) {
197                 rotate = ROTATE_180_DEGREES;
198             } else if (strcmp(attributes[i + 1], "270") == 0) {
199                 rotate = ROTATE_270_DEGREES;
200             }
201         }
202         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][0][7]) == 0) {
203             if (strcmp(attributes[i + 1], "footprint") == 0) {
204                 part = PART_FOOTPRINT;
205             } else if (strcmp(attributes[i + 1], "top") == 0) {
206                 part = PART_TOP;
207             }
208         }
209     }
210     if (!asset_image_add_layer(img, path, group, id, offset_x, offset_y, invert, rotate, part)) {
211         log_info("Invalid layer for image", img->id, 0);
212         return;
213     }
214 }
215 
xml_start_animation_element(const char ** attributes)216 static void xml_start_animation_element(const char **attributes)
217 {
218     asset_image *img = data.current_image;
219     if (img->img.num_animation_sprites) {
220         return;
221     }
222     int total_attributes = count_xml_attributes(attributes);
223     if (total_attributes % 2) {
224         data.error = 1;
225         return;
226     }
227 
228     for (int i = 0; i < total_attributes; i += 2) {
229         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][1][0]) == 0) {
230             img->img.num_animation_sprites = string_to_int(string_from_ascii(attributes[i + 1]));
231         }
232         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][1][1]) == 0) {
233             img->img.animation_speed_id = calc_bound(string_to_int(string_from_ascii(attributes[i + 1])), 0, 50);
234         }
235         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][1][2]) == 0) {
236             const char *value = attributes[i + 1];
237             if (strcmp(value, "true") == 0 || strcmp(value, "1") == 0 || strcmp(value, "reversible") == 0 ||
238                 strcmp(value, "yes") == 0 || strcmp(value, "y") == 0) {
239                 img->img.animation_can_reverse = 1;
240             }
241         }
242         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][1][3]) == 0) {
243             img->img.sprite_offset_x = string_to_int(string_from_ascii(attributes[i + 1]));
244         }
245         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[2][1][4]) == 0) {
246             img->img.sprite_offset_y = string_to_int(string_from_ascii(attributes[i + 1]));
247         }
248     }
249     if (!img->img.num_animation_sprites) {
250         data.in_animation = 1;
251     }
252 }
253 
xml_start_frame_element(const char ** attributes)254 static void xml_start_frame_element(const char **attributes)
255 {
256     int total_attributes = count_xml_attributes(attributes);
257     if (!data.in_animation || total_attributes < 2 || total_attributes % 2) {
258         return;
259     }
260     asset_image *img = asset_image_create();
261     if (!img) {
262         data.error = 1;
263         return;
264     }
265     const char *path = 0;
266     const char *group = 0;
267     const char *id = 0;
268     for (int i = 0; i < total_attributes; i += 2) {
269         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[3][0][0]) == 0) {
270             path = attributes[i + 1];
271         }
272         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[3][0][1]) == 0) {
273             img->img.width = string_to_int(string_from_ascii(attributes[i + 1]));
274         }
275         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[3][0][2]) == 0) {
276             img->img.height = string_to_int(string_from_ascii(attributes[i + 1]));
277         }
278         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[3][0][3]) == 0) {
279             group = attributes[i + 1];
280         }
281         if (strcmp(attributes[i], XML_FILE_ATTRIBUTES[3][0][4]) == 0) {
282             id = attributes[i + 1];
283         }
284     }
285     img->last_layer = &img->first_layer;
286     if (!path && !(group && id)) {
287         img->active = 0;
288         return;
289     }
290     asset_image_add_layer(img, path, group, id, 0, 0, INVERT_NONE, ROTATE_NONE, PART_BOTH);
291     img->img.draw.data_length = img->img.width * img->img.height * sizeof(color_t);
292     img->img.draw.uncompressed_length = img->img.draw.data_length;
293     if (!img->img.draw.data_length) {
294         asset_image_unload(img);
295         return;
296     }
297     img->img.draw.type = IMAGE_TYPE_EXTRA_ASSET;
298     asset_image_load(img);
299 
300     data.current_image->img.num_animation_sprites++;
301 }
302 
xml_end_assetlist_element(void)303 static void xml_end_assetlist_element(void)
304 {
305     data.finished = 1;
306     data.current_group = 0;
307 }
308 
xml_end_image_element(void)309 static void xml_end_image_element(void)
310 {
311     image *img = &data.current_image->img;
312     img->draw.data_length = img->width * img->height * sizeof(color_t);
313     img->draw.uncompressed_length = img->draw.data_length;
314     if (!img->draw.data_length) {
315         asset_image_unload(data.current_image);
316         return;
317     }
318     img->draw.type = IMAGE_TYPE_EXTRA_ASSET;
319     if (img->draw.data_length < IMAGE_PRELOAD_MAX_SIZE) {
320         asset_image_load(data.current_image);
321     }
322 }
323 
xml_end_layer_element(void)324 static void xml_end_layer_element(void)
325 {}
326 
xml_end_animation_element(void)327 static void xml_end_animation_element(void)
328 {
329     data.in_animation = 0;
330 }
331 
xml_end_frame_element(void)332 static void xml_end_frame_element(void)
333 {}
334 
get_element_index(const char * name)335 static int get_element_index(const char *name)
336 {
337     for (int i = 0; i < XML_MAX_ELEMENTS_PER_DEPTH; ++i) {
338         if (XML_FILE_ELEMENTS[data.depth][i][0] == 0) {
339             continue;
340         }
341         if (strcmp(XML_FILE_ELEMENTS[data.depth][i], name) == 0) {
342             return i;
343         }
344     }
345     return -1;
346 }
347 
xml_start_element(void * unused,const char * name,const char ** attributes)348 static void XMLCALL xml_start_element(void *unused, const char *name, const char **attributes)
349 {
350     if (data.error) {
351         return;
352     }
353     if (data.finished || data.depth == XML_MAX_DEPTH) {
354         data.error = 1;
355         log_error("Invalid XML parameter", name, 0);
356         return;
357     }
358     int index = get_element_index(name);
359     if (index == -1) {
360         data.error = 1;
361         log_error("Invalid XML parameter", name, 0);
362         return;
363     }
364     (*xml_start_element_callback[data.depth][index])(attributes);
365     data.depth++;
366 }
367 
xml_end_element(void * unused,const char * name)368 static void XMLCALL xml_end_element(void *unused, const char *name)
369 {
370     if (data.error) {
371         return;
372     }
373     data.depth--;
374     int index = get_element_index(name);
375     if (index == -1) {
376         data.error = 1;
377         log_error("Invalid XML parameter", name, 0);
378         return;
379     }
380     (*xml_end_element_callback[data.depth][index])();
381 }
382 
clear_xml_info(void)383 static void clear_xml_info(void)
384 {
385     data.error = 0;
386     data.depth = 0;
387     data.finished = 0;
388 }
389 
xml_process_assetlist_file(const char * xml_file_name)390 void xml_process_assetlist_file(const char *xml_file_name)
391 {
392     log_info("Loading assetlist file", xml_file_name, 0);
393 
394     FILE *xml_file = file_open_asset(xml_file_name, "r");
395 
396     if (!xml_file) {
397         log_error("Error opening assetlist file", xml_file_name, 0);
398         return;
399     }
400 
401     XML_Parser parser = XML_ParserCreate(NULL);
402     XML_SetHashSalt(parser, XML_HASH_SEED);
403     XML_SetElementHandler(parser, xml_start_element, xml_end_element);
404 
405     char buffer[XML_BUFFER_SIZE];
406     int done = 0;
407 
408     do {
409         size_t bytes_read = fread(buffer, 1, XML_BUFFER_SIZE, xml_file);
410         done = bytes_read < sizeof(buffer);
411         if (XML_Parse(parser, buffer, (int) bytes_read, done) == XML_STATUS_ERROR || data.error) {
412             log_error("Error parsing file", xml_file_name, 0);
413             break;
414         }
415     } while (!done);
416 
417     if (data.current_group && (data.error || !data.finished)) {
418         group_unload_current();
419     }
420 
421     clear_xml_info();
422 
423     XML_ParserFree(parser);
424     file_close(xml_file);
425 }
426 
xml_get_full_image_path(char * full_path,const char * image_file_name)427 void xml_get_full_image_path(char *full_path, const char *image_file_name)
428 {
429     strncpy(full_path, data.file_name, data.file_name_position);
430     size_t file_name_size = strlen(image_file_name);
431     strncpy(full_path + data.file_name_position, image_file_name, FILE_NAME_MAX - data.file_name_position);
432     strncpy(full_path + data.file_name_position + file_name_size, ".png",
433         FILE_NAME_MAX - data.file_name_position - file_name_size);
434 }
435