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