cdrom: adjust timing
[pcsx_rearmed.git] / deps / libretro-common / formats / logiqx_dat / logiqx_dat.c
1 /* Copyright  (C) 2010-2020 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (logiqx_dat.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22
23 #include <file/file_path.h>
24 #include <string/stdstring.h>
25 #include <formats/rxml.h>
26
27 #include <formats/logiqx_dat.h>
28
29 /* Holds all internal DAT file data */
30 struct logiqx_dat
31 {
32    rxml_document_t *data;
33    rxml_node_t *current_node;
34 };
35
36 /* List of HTML formatting codes that must
37  * be replaced when parsing XML data */
38 const char *logiqx_dat_html_code_list[][2] = { 
39    {"&amp;",  "&"},
40    {"&apos;", "'"},
41    {"&gt;",   ">"}, 
42    {"&lt;",   "<"},
43    {"&quot;", "\""} 
44 };
45
46 #define LOGIQX_DAT_HTML_CODE_LIST_SIZE 5
47
48 /* Validation */
49
50 /* Performs rudimentary validation of the specified
51  * Logiqx XML DAT file path (not rigorous - just
52  * enough to prevent obvious errors).
53  * Also provides access to file size (DAT files can
54  * be very large, so it is useful to have this information
55  * on hand - i.e. so we can check that the system has
56  * enough free memory to load the file). */ 
57 bool logiqx_dat_path_is_valid(const char *path, uint64_t *file_size)
58 {
59    const char *file_ext = NULL;
60    int32_t file_size_int;
61
62    if (string_is_empty(path))
63       return false;
64
65    /* Check file extension */
66    file_ext = path_get_extension(path);
67
68    if (string_is_empty(file_ext))
69       return false;
70
71    if (!string_is_equal_noncase(file_ext, "dat") &&
72        !string_is_equal_noncase(file_ext, "xml"))
73       return false;
74
75    /* Ensure file exists */
76    if (!path_is_valid(path))
77       return false;
78
79    /* Get file size */
80    file_size_int = path_get_size(path);
81
82    if (file_size_int <= 0)
83       return false;
84
85    if (file_size)
86       *file_size = (uint64_t)file_size_int;
87
88    return true;
89 }
90
91 /* File initialisation/de-initialisation */
92
93 /* Loads specified Logiqx XML DAT file from disk.
94  * Returned logiqx_dat_t object must be free'd using
95  * logiqx_dat_free().
96  * Returns NULL if file is invalid or a read error
97  * occurs. */
98 logiqx_dat_t *logiqx_dat_init(const char *path)
99 {
100    logiqx_dat_t *dat_file = NULL;
101    rxml_node_t *root_node = NULL;
102
103    /* Check file path */
104    if (!logiqx_dat_path_is_valid(path, NULL))
105       goto error;
106
107    /* Create logiqx_dat_t object */
108    dat_file = (logiqx_dat_t*)calloc(1, sizeof(*dat_file));
109
110    if (!dat_file)
111       goto error;
112
113    /* Read file from disk */
114    dat_file->data = rxml_load_document(path);
115
116    if (!dat_file->data)
117       goto error;
118
119    /* Ensure root node has the correct name */
120    root_node = rxml_root_node(dat_file->data);
121
122    if (!root_node)
123       goto error;
124
125    if (string_is_empty(root_node->name))
126       goto error;
127
128    /* > Logiqx XML uses:           'datafile'
129     * > MAME List XML uses:        'mame'
130     * > MAME 'Software List' uses: 'softwarelist' */
131    if (!string_is_equal(root_node->name, "datafile") &&
132        !string_is_equal(root_node->name, "mame") &&
133        !string_is_equal(root_node->name, "softwarelist"))
134       goto error;
135
136    /* Get pointer to initial child node */
137    dat_file->current_node = root_node->children;
138
139    if (!dat_file->current_node)
140       goto error;
141
142    /* All is well - return logiqx_dat_t object */
143    return dat_file;
144
145 error:
146    logiqx_dat_free(dat_file);
147    return NULL;
148 }
149
150 /* Frees specified DAT file */
151 void logiqx_dat_free(logiqx_dat_t *dat_file)
152 {
153    if (!dat_file)
154       return;
155
156    dat_file->current_node = NULL;
157
158    if (dat_file->data)
159    {
160       rxml_free_document(dat_file->data);
161       dat_file->data = NULL;
162    }
163
164    free(dat_file);
165    dat_file = NULL;
166 }
167
168 /* Game information access */
169
170 /* Returns true if specified node is a 'game' entry */
171 static bool logiqx_dat_is_game_node(rxml_node_t *node)
172 {
173    const char *node_name = NULL;
174
175    if (!node)
176       return false;
177
178    /* Check node name */
179    node_name = node->name;
180
181    if (string_is_empty(node_name))
182       return false;
183
184    /* > Logiqx XML uses:           'game'
185     * > MAME List XML uses:        'machine'
186     * > MAME 'Software List' uses: 'software' */
187    return string_is_equal(node_name, "game") ||
188           string_is_equal(node_name, "machine") ||
189           string_is_equal(node_name, "software");
190 }
191
192 /* Returns true if specified node is a game
193  * node containing information for a game with
194  * the specified name */
195 static bool logiqx_dat_game_node_matches_name(
196       rxml_node_t *node, const char *game_name)
197 {
198    const char *node_game_name = NULL;
199
200    if (!logiqx_dat_is_game_node(node) ||
201        string_is_empty(game_name))
202       return false;
203
204    /* Get 'name' attribute of XML node */
205    node_game_name = rxml_node_attrib(node, "name");
206
207    if (string_is_empty(node_game_name))
208       return false;
209
210    return string_is_equal(node_game_name, game_name);
211 }
212
213 /* The XML element data strings returned from
214  * DAT files are very 'messy'. This function
215  * removes all cruft, replaces formatting strings
216  * and copies the result (if valid) to 'str' */
217 static void logiqx_dat_sanitise_element_data(
218       const char *data, char *str, size_t len)
219 {
220    char sanitised_data[PATH_MAX_LENGTH];
221    size_t i;
222
223    sanitised_data[0] = '\0';
224
225    if (string_is_empty(data))
226       return;
227
228    strlcpy(sanitised_data, data, sizeof(sanitised_data));
229
230    /* Element data includes leading/trailing
231     * newline characters - trim them away */
232    string_trim_whitespace(sanitised_data);
233
234    if (string_is_empty(sanitised_data))
235       return;
236
237    /* XML has a number of special characters that
238     * are handled using a HTML formatting codes.
239     * All of these have to be replaced...
240     * &amp;  -> &
241     * &apos; -> '
242     * &gt;   -> >
243     * &lt;   -> <
244     * &quot; -> "
245     */
246    for (i = 0; i < LOGIQX_DAT_HTML_CODE_LIST_SIZE; i++)
247    {
248       const char *find_string    = logiqx_dat_html_code_list[i][0];
249       const char *replace_string = logiqx_dat_html_code_list[i][1];
250
251       /* string_replace_substring() is expensive
252        * > only invoke if element string contains
253        *   HTML code */
254       if (strstr(sanitised_data, find_string))
255       {
256          char *tmp = string_replace_substring(
257                sanitised_data,
258                find_string,    strlen(find_string),
259                replace_string, strlen(replace_string));
260
261          if (!string_is_empty(tmp))
262             strlcpy(sanitised_data, tmp, sizeof(sanitised_data));
263
264          if (tmp)
265             free(tmp);
266       }
267    }
268
269    if (string_is_empty(sanitised_data))
270       return;
271
272    /* All is well - can copy result */
273    strlcpy(str, sanitised_data, len);
274 }
275
276 /* Extracts game information from specified node.
277  * Returns false if node is invalid */
278 static bool logiqx_dat_parse_game_node(
279       rxml_node_t *node, logiqx_dat_game_info_t *game_info)
280 {
281    const char *game_name   = NULL;
282    const char *is_bios     = NULL;
283    const char *is_runnable = NULL;
284    rxml_node_t *info_node  = NULL;
285    bool description_found  = false;
286    bool year_found         = false;
287    bool manufacturer_found = false;
288
289    if (!logiqx_dat_is_game_node(node))
290       return false;
291
292    if (!game_info)
293       return false;
294
295    /* Initialise logiqx_dat_game_info_t object */
296    game_info->name[0]         = '\0';
297    game_info->description[0]  = '\0';
298    game_info->year[0]         = '\0';
299    game_info->manufacturer[0] = '\0';
300    game_info->is_bios         = false;
301    game_info->is_runnable     = true;
302
303    /* Get game name */
304    game_name = rxml_node_attrib(node, "name");
305
306    if (!string_is_empty(game_name))
307       strlcpy(game_info->name, game_name, sizeof(game_info->name));
308
309    /* Get 'is bios' status */
310    is_bios = rxml_node_attrib(node, "isbios");
311
312    if (!string_is_empty(is_bios))
313       game_info->is_bios = string_is_equal(is_bios, "yes");
314
315    /* Get 'is runnable' status
316     * > Note: This attribute only exists in MAME List
317     *   XML files, but there is no harm in checking for
318     *   it generally. For normal Logiqx XML files,
319     *   'is runnable' is just the inverse of 'is bios' */
320    is_runnable = rxml_node_attrib(node, "runnable");
321
322    if (!string_is_empty(is_runnable))
323       game_info->is_runnable = string_is_equal(is_runnable, "yes");
324    else
325       game_info->is_runnable = !game_info->is_bios;
326
327    /* Loop over all game info nodes */
328    for (info_node = node->children; info_node; info_node = info_node->next)
329    {
330       const char *info_node_name = info_node->name;
331       const char *info_node_data = info_node->data;
332
333       if (string_is_empty(info_node_name))
334          continue;
335
336       /* Check description */
337       if (string_is_equal(info_node_name, "description"))
338       {
339          logiqx_dat_sanitise_element_data(
340             info_node_data, game_info->description,
341             sizeof(game_info->description));
342          description_found = true;
343       }
344       /* Check year */
345       else if (string_is_equal(info_node_name, "year"))
346       {
347          logiqx_dat_sanitise_element_data(
348             info_node_data, game_info->year,
349             sizeof(game_info->year));
350          year_found = true;
351       }
352       /* Check manufacturer */
353       else if (string_is_equal(info_node_name, "manufacturer"))
354       {
355          logiqx_dat_sanitise_element_data(
356             info_node_data, game_info->manufacturer,
357             sizeof(game_info->manufacturer));
358          manufacturer_found = true;
359       }
360
361       /* If all required entries have been found,
362        * can end loop */
363       if (description_found && year_found && manufacturer_found)
364          break;
365    }
366
367    return true;
368 }
369
370 /* Sets/resets internal node pointer to the first
371  * entry in the DAT file */
372 void logiqx_dat_set_first(logiqx_dat_t *dat_file)
373 {
374    rxml_node_t *root_node = NULL;
375
376    if (!dat_file)
377       return;
378
379    if (!dat_file->data)
380       return;
381
382    /* Get root node */
383    root_node = rxml_root_node(dat_file->data);
384
385    if (!root_node)
386    {
387       dat_file->current_node = NULL;
388       return;
389    }
390
391    /* Get pointer to initial child node */
392    dat_file->current_node = root_node->children;
393 }
394
395 /* Fetches game information for the current entry
396  * in the DAT file and increments the internal node
397  * pointer.
398  * Returns false if the end of the DAT file has been
399  * reached (in which case 'game_info' will be invalid) */
400 bool logiqx_dat_get_next(
401       logiqx_dat_t *dat_file, logiqx_dat_game_info_t *game_info)
402 {
403    if (!dat_file || !game_info)
404       return false;
405
406    if (!dat_file->data)
407       return false;
408
409    while (dat_file->current_node)
410    {
411       rxml_node_t *current_node = dat_file->current_node;
412
413       /* Whatever happens, internal node pointer must
414        * be 'incremented' */
415       dat_file->current_node = dat_file->current_node->next;
416
417       /* If this is a game node, extract info
418        * and return */
419       if (logiqx_dat_is_game_node(current_node))
420          return logiqx_dat_parse_game_node(current_node, game_info);
421    }
422
423    return false;
424 }
425
426 /* Fetches information for the specified game.
427  * Returns false if game does not exist, or arguments
428  * are invalid. */
429 bool logiqx_dat_search(
430       logiqx_dat_t *dat_file, const char *game_name,
431       logiqx_dat_game_info_t *game_info)
432 {
433    rxml_node_t *root_node = NULL;
434    rxml_node_t *game_node = NULL;
435
436    if (!dat_file || !game_info || string_is_empty(game_name))
437       return false;
438
439    if (!dat_file->data)
440       return false;
441
442    /* Get root node */
443    root_node = rxml_root_node(dat_file->data);
444
445    if (!root_node)
446       return false;
447
448    /* Loop over all child nodes of the DAT file */
449    for (game_node = root_node->children; game_node; game_node = game_node->next)
450    {
451       /* If this is the requested game, fetch info and return */
452       if (logiqx_dat_game_node_matches_name(game_node, game_name))
453          return logiqx_dat_parse_game_node(game_node, game_info);
454    }
455
456    return false;
457 }