1 /****************************************************************************
2 * datafile.c
3 * History database engine
4 *
5 * Token parsing by Neil Bradley
6 * Modifications and higher-level functions by John Butler
7 ****************************************************************************/
8
9 #include <assert.h>
10 #include <ctype.h>
11
12 #include "osd_cpu.h"
13 #include "driver.h"
14 #include "datafile.h"
15
16 /****************************************************************************
17 * token parsing constants
18 ****************************************************************************/
19 #define CR 0x0d /* '\n' and '\r' meanings are swapped in some */
20 #define LF 0x0a /* compilers (e.g., Mac compilers) */
21
22 enum
23 {
24 TOKEN_COMMA,
25 TOKEN_EQUALS,
26 TOKEN_SYMBOL,
27 TOKEN_LINEBREAK,
28 TOKEN_INVALID=-1
29 };
30
31 #define MAX_TOKEN_LENGTH 256
32
33
34 /****************************************************************************
35 * datafile constants
36 ****************************************************************************/
37 #define MAX_DATAFILE_ENTRIES 5000
38 #define DATAFILE_TAG '$'
39
40 const char *DATAFILE_TAG_KEY = "$info";
41 const char *DATAFILE_TAG_BIO = "$bio";
42 const char *DATAFILE_TAG_MAME = "$mame";
43
44 const char *history_filename = NULL;
45 const char *mameinfo_filename = NULL;
46
47
48 /****************************************************************************
49 * private data for parsing functions
50 ****************************************************************************/
51 static mame_file *fp; /* Our file pointer */
52 static long dwFilePos; /* file position */
53 static UINT8 bToken[MAX_TOKEN_LENGTH]; /* Our current token */
54
55 /* an array of driver name/drivers array index sorted by driver name
56 for fast look up by name */
57 typedef struct
58 {
59 const char *name;
60 int index;
61 } driver_data_type;
62 static driver_data_type *sorted_drivers = NULL;
63 static int num_games;
64
65
66 /**************************************************************************
67 **************************************************************************
68 *
69 * Parsing functions
70 *
71 **************************************************************************
72 **************************************************************************/
73
74 /*
75 * DriverDataCompareFunc -- compare function for GetGameNameIndex
76 */
DriverDataCompareFunc(const void * arg1,const void * arg2)77 static int CLIB_DECL DriverDataCompareFunc(const void *arg1,const void *arg2)
78 {
79 return strcmp( ((driver_data_type *)arg1)->name, ((driver_data_type *)arg2)->name );
80 }
81
82 /*
83 * GetGameNameIndex -- given a driver name (in lowercase), return
84 * its index in the main drivers[] array, or -1 if it's not found.
85 */
GetGameNameIndex(const char * name)86 static int GetGameNameIndex(const char *name)
87 {
88 driver_data_type *driver_index_info;
89 driver_data_type key;
90 key.name = name;
91
92 if (sorted_drivers == NULL)
93 {
94 /* initialize array of game names/indices */
95 int i;
96 num_games = 0;
97 while (drivers[num_games] != NULL)
98 num_games++;
99
100 sorted_drivers = (driver_data_type *)malloc(sizeof(driver_data_type) * num_games);
101 for (i=0;i<num_games;i++)
102 {
103 sorted_drivers[i].name = drivers[i]->name;
104 sorted_drivers[i].index = i;
105 }
106 qsort(sorted_drivers,num_games,sizeof(driver_data_type),DriverDataCompareFunc);
107 }
108
109 /* uses our sorted array of driver names to get the index in log time */
110 driver_index_info = bsearch(&key,sorted_drivers,num_games,sizeof(driver_data_type),
111 DriverDataCompareFunc);
112
113 if (driver_index_info == NULL)
114 return -1;
115
116 return driver_index_info->index;
117
118 }
119
120 /****************************************************************************
121 * GetNextToken - Pointer to the token string pointer
122 * Pointer to position within file
123 *
124 * Returns token, or TOKEN_INVALID if at end of file
125 ****************************************************************************/
GetNextToken(UINT8 ** ppszTokenText,long * pdwPosition)126 static UINT32 GetNextToken(UINT8 **ppszTokenText, long *pdwPosition)
127 {
128 UINT32 dwLength; /* Length of symbol */
129 long dwPos; /* Temporary position */
130 UINT8 *pbTokenPtr = bToken; /* Point to the beginning */
131 UINT8 bData; /* Temporary data byte */
132
133 while (1)
134 {
135 bData = mame_fgetc(fp); /* Get next character */
136
137 /* If we're at the end of the file, bail out */
138
139 if (mame_feof(fp))
140 return(TOKEN_INVALID);
141
142 /* If it's not whitespace, then let's start eating characters */
143
144 if (' ' != bData && '\t' != bData)
145 {
146 /* Store away our file position (if given on input) */
147
148 if (pdwPosition)
149 *pdwPosition = dwFilePos;
150
151 /* If it's a separator, special case it */
152
153 if (',' == bData || '=' == bData)
154 {
155 *pbTokenPtr++ = bData;
156 *pbTokenPtr = '\0';
157 ++dwFilePos;
158
159 if (',' == bData)
160 return(TOKEN_COMMA);
161 else
162 return(TOKEN_EQUALS);
163 }
164
165 /* Otherwise, let's try for a symbol */
166
167 if (bData > ' ')
168 {
169 dwLength = 0; /* Assume we're 0 length to start with */
170
171 /* Loop until we've hit something we don't understand */
172
173 while (bData != ',' &&
174 bData != '=' &&
175 bData != ' ' &&
176 bData != '\t' &&
177 bData != '\n' &&
178 bData != '\r' &&
179 mame_feof(fp) == 0)
180 {
181 ++dwFilePos;
182 *pbTokenPtr++ = bData; /* Store our byte */
183 ++dwLength;
184 assert(dwLength < MAX_TOKEN_LENGTH);
185 bData = mame_fgetc(fp);
186 }
187
188 /* If it's not the end of the file, put the last received byte */
189 /* back. We don't want to touch the file position, though if */
190 /* we're past the end of the file. Otherwise, adjust it. */
191
192 if (0 == mame_feof(fp))
193 {
194 mame_ungetc(bData, fp);
195 }
196
197 /* Null terminate the token */
198
199 *pbTokenPtr = '\0';
200
201 /* Connect up the */
202
203 if (ppszTokenText)
204 *ppszTokenText = bToken;
205
206 return(TOKEN_SYMBOL);
207 }
208
209 /* Not a symbol. Let's see if it's a cr/cr, lf/lf, or cr/lf/cr/lf */
210 /* sequence */
211
212 if (LF == bData)
213 {
214 /* Unix style perhaps? */
215
216 bData = mame_fgetc(fp); /* Peek ahead */
217 mame_ungetc(bData, fp); /* Force a retrigger if subsequent LF's */
218
219 if (LF == bData) /* Two LF's in a row - it's a UNIX hard CR */
220 {
221 ++dwFilePos;
222 *pbTokenPtr++ = bData; /* A real linefeed */
223 *pbTokenPtr = '\0';
224 return(TOKEN_LINEBREAK);
225 }
226
227 /* Otherwise, fall through and keep parsing. */
228
229 }
230 else
231 if (CR == bData) /* Carriage return? */
232 {
233 /* Figure out if it's Mac or MSDOS format */
234
235 ++dwFilePos;
236 bData = mame_fgetc(fp); /* Peek ahead */
237
238 /* We don't need to bother with EOF checking. It will be 0xff if */
239 /* it's the end of the file and will be caught by the outer loop. */
240
241 if (CR == bData) /* Mac style hard return! */
242 {
243 /* Do not advance the file pointer in case there are successive */
244 /* CR/CR sequences */
245
246 /* Stuff our character back upstream for successive CR's */
247
248 mame_ungetc(bData, fp);
249
250 *pbTokenPtr++ = bData; /* A real carriage return (hard) */
251 *pbTokenPtr = '\0';
252 return(TOKEN_LINEBREAK);
253 }
254 else
255 if (LF == bData) /* MSDOS format! */
256 {
257 ++dwFilePos; /* Our file position to reset to */
258 dwPos = dwFilePos; /* Here so we can reposition things */
259
260 /* Look for a followup CR/LF */
261
262 bData = mame_fgetc(fp); /* Get the next byte */
263
264 if (CR == bData) /* CR! Good! */
265 {
266 bData = mame_fgetc(fp); /* Get the next byte */
267
268 /* We need to do this to pick up subsequent CR/LF sequences */
269
270 mame_fseek(fp, dwPos, SEEK_SET);
271
272 if (pdwPosition)
273 *pdwPosition = dwPos;
274
275 if (LF == bData) /* LF? Good! */
276 {
277 *pbTokenPtr++ = '\r';
278 *pbTokenPtr++ = '\n';
279 *pbTokenPtr = '\0';
280
281 return(TOKEN_LINEBREAK);
282 }
283 }
284 else
285 {
286 --dwFilePos;
287 mame_ungetc(bData, fp); /* Put the character back. No good */
288 }
289 }
290 else
291 {
292 --dwFilePos;
293 mame_ungetc(bData, fp); /* Put the character back. No good */
294 }
295
296 /* Otherwise, fall through and keep parsing */
297 }
298 }
299
300 ++dwFilePos;
301 }
302 }
303
304
305 /****************************************************************************
306 * ParseClose - Closes the existing opened file (if any)
307 ****************************************************************************/
ParseClose(void)308 static void ParseClose(void)
309 {
310 /* If the file is open, time for fclose. */
311
312 if (fp)
313 {
314 mame_fclose(fp);
315 }
316
317 fp = NULL;
318 }
319
320
321 /****************************************************************************
322 * ParseOpen - Open up file for reading
323 ****************************************************************************/
ParseOpen(const char * pszFilename)324 static UINT8 ParseOpen(const char *pszFilename)
325 {
326 /* Open file up in binary mode */
327 fp = mame_fopen (NULL, pszFilename, FILETYPE_HISTORY, 0);
328
329 /* If this is NULL, return FALSE. We can't open it */
330
331 if (!fp)
332 return 0;
333
334 /* Otherwise, prepare! */
335
336 dwFilePos = 0;
337 return 1;
338 }
339
340
341 /****************************************************************************
342 * ParseSeek - Move the file position indicator
343 ****************************************************************************/
ParseSeek(long offset,int whence)344 static UINT8 ParseSeek(long offset, int whence)
345 {
346 int result = mame_fseek(fp, offset, whence);
347
348 if (0 == result)
349 dwFilePos = mame_ftell(fp);
350 return (UINT8)result;
351 }
352
353
354
355 /**************************************************************************
356 **************************************************************************
357 *
358 * Datafile functions
359 *
360 **************************************************************************
361 **************************************************************************/
362
363
364 /**************************************************************************
365 * ci_strcmp - case insensitive string compare
366 *
367 * Returns zero if s1 and s2 are equal, ignoring case
368 **************************************************************************/
ci_strcmp(const char * s1,const char * s2)369 static int ci_strcmp (const char *s1, const char *s2)
370 {
371 int c1, c2;
372
373 while ((c1 = tolower(*s1)) == (c2 = tolower(*s2)))
374 {
375 if (!c1)
376 return 0;
377
378 s1++;
379 s2++;
380 }
381
382 return (c1 - c2);
383 }
384
385
386 /**************************************************************************
387 * ci_strncmp - case insensitive character array compare
388 *
389 * Returns zero if the first n characters of s1 and s2 are equal,
390 * ignoring case.
391 **************************************************************************/
ci_strncmp(const char * s1,const char * s2,int n)392 static int ci_strncmp (const char *s1, const char *s2, int n)
393 {
394 int c1, c2;
395
396 while (n)
397 {
398 if ((c1 = tolower (*s1)) != (c2 = tolower (*s2)))
399 return (c1 - c2);
400 else if (!c1)
401 break;
402 --n;
403
404 s1++;
405 s2++;
406 }
407 return 0;
408 }
409
410
411 /**************************************************************************
412 * index_datafile
413 * Create an index for the records in the currently open datafile.
414 *
415 * Returns 0 on error, or the number of index entries created.
416 **************************************************************************/
index_datafile(struct tDatafileIndex ** _index)417 static int index_datafile (struct tDatafileIndex **_index)
418 {
419 struct tDatafileIndex *idx;
420 int count = 0;
421 UINT32 token = TOKEN_SYMBOL;
422
423 /* rewind file */
424 if (ParseSeek (0L, SEEK_SET)) return 0;
425
426 /* allocate index */
427 idx = *_index = malloc (MAX_DATAFILE_ENTRIES * sizeof (struct tDatafileIndex));
428 if (NULL == idx) return 0;
429
430 /* loop through datafile */
431 while ((count < (MAX_DATAFILE_ENTRIES - 1)) && TOKEN_INVALID != token)
432 {
433 long tell;
434 char *s;
435
436 token = GetNextToken ((UINT8 **)&s, &tell);
437 if (TOKEN_SYMBOL != token) continue;
438
439 /* DATAFILE_TAG_KEY identifies the driver */
440 if (!ci_strncmp (DATAFILE_TAG_KEY, s, strlen (DATAFILE_TAG_KEY)))
441 {
442 token = GetNextToken ((UINT8 **)&s, &tell);
443 if (TOKEN_EQUALS == token)
444 {
445 int done = 0;
446
447 token = GetNextToken ((UINT8 **)&s, &tell);
448 while (!done && TOKEN_SYMBOL == token)
449 {
450 int game_index;
451 char *p;
452 for (p = s; *p; p++)
453 *p = tolower(*p);
454
455 game_index = GetGameNameIndex(s);
456 if (game_index >= 0)
457 {
458 idx->driver = drivers[game_index];
459 idx->offset = tell;
460 idx++;
461 count++;
462 /* done = 1; Not done, as we must process other clones in list */
463
464 }
465 if (!done)
466 {
467 token = GetNextToken ((UINT8 **)&s, &tell);
468
469 if (TOKEN_COMMA == token)
470 token = GetNextToken ((UINT8 **)&s, &tell);
471 else
472 done = 1; /* end of key field */
473 }
474 }
475 }
476 }
477 }
478
479 /* mark end of index */
480 idx->offset = 0L;
481 idx->driver = 0;
482 return count;
483 }
484
485
486 /**************************************************************************
487 * load_datafile_text
488 *
489 * Loads text field for a driver into the buffer specified. Specify the
490 * driver, a pointer to the buffer, the buffer size, the index created by
491 * index_datafile(), and the desired text field (e.g., DATAFILE_TAG_BIO).
492 *
493 * Returns 0 if successful.
494 **************************************************************************/
load_datafile_text(const struct GameDriver * drv,char * buffer,int bufsize,struct tDatafileIndex * idx,const char * tag)495 static int load_datafile_text (const struct GameDriver *drv, char *buffer, int bufsize,
496 struct tDatafileIndex *idx, const char *tag)
497 {
498 int offset = 0;
499 int found = 0;
500 UINT32 token = TOKEN_SYMBOL;
501 UINT32 prev_token = TOKEN_SYMBOL;
502
503 *buffer = '\0';
504
505 /* find driver in datafile index */
506 while (idx->driver)
507 {
508
509 if (idx->driver == drv) break;
510
511 idx++;
512 }
513 if (idx->driver == 0) return 1; /* driver not found in index */
514
515 /* seek to correct point in datafile */
516 if (ParseSeek (idx->offset, SEEK_SET)) return 1;
517
518 /* read text until buffer is full or end of entry is encountered */
519 while (TOKEN_INVALID != token)
520 {
521 char *s;
522 int len;
523 long tell;
524
525 token = GetNextToken ((UINT8 **)&s, &tell);
526 if (TOKEN_INVALID == token) continue;
527
528 if (found)
529 {
530 /* end entry when a tag is encountered */
531 if (TOKEN_SYMBOL == token && DATAFILE_TAG == s[0] && TOKEN_LINEBREAK == prev_token) break;
532
533 prev_token = token;
534
535 /* translate platform-specific linebreaks to '\n' */
536 if (TOKEN_LINEBREAK == token)
537 strcpy (s, "\n");
538
539 /* append a space to words */
540 if (TOKEN_LINEBREAK != token)
541 strcat (s, " ");
542
543 /* remove extraneous space before commas */
544 if (TOKEN_COMMA == token)
545 {
546 --buffer;
547 --offset;
548 *buffer = '\0';
549 }
550
551 /* Get length of text to add to the buffer */
552 len = strlen (s);
553
554 /* Check for buffer overflow */
555 /* For some reason we can get a crash if we try */
556 /* to use the last 30 characters of the buffer */
557 if ((bufsize - offset) - len <= 45)
558 {
559 strcpy (s, " ...[TRUNCATED]");
560 len = strlen(s);
561 strcpy (buffer, s);
562 buffer += len;
563 offset += len;
564 break;
565 }
566
567 /* add this word to the buffer */
568 strcpy (buffer, s);
569 buffer += len;
570 offset += len;
571 }
572 else
573 {
574 if (TOKEN_SYMBOL == token)
575 {
576 /* looking for requested tag */
577 if (!ci_strncmp (tag, s, strlen (tag)))
578 found = 1;
579 else if (!ci_strncmp (DATAFILE_TAG_KEY, s, strlen (DATAFILE_TAG_KEY)))
580 break; /* error: tag missing */
581 }
582 }
583 }
584 return (!found);
585 }
586
587
588 /**************************************************************************
589 * load_driver_history
590 * Load history text for the specified driver into the specified buffer.
591 * Combines $bio field of HISTORY.DAT with $mame field of MAMEINFO.DAT.
592 *
593 * Returns 0 if successful.
594 *
595 * NOTE: For efficiency the indices are never freed (intentional leak).
596 **************************************************************************/
load_driver_history(const struct GameDriver * drv,char * buffer,int bufsize)597 int load_driver_history (const struct GameDriver *drv, char *buffer, int bufsize)
598 {
599 static struct tDatafileIndex *hist_idx = 0;
600 static struct tDatafileIndex *mame_idx = 0;
601 int history = 0, mameinfo = 0;
602 int err;
603
604 *buffer = 0;
605
606
607 if(!history_filename)
608 history_filename = "history.dat";
609
610 /* try to open history datafile */
611 if (ParseOpen (history_filename))
612 {
613 /* create index if necessary */
614 if (hist_idx)
615 history = 1;
616 else
617 history = (index_datafile (&hist_idx) != 0);
618
619 /* load history text */
620 if (hist_idx)
621 {
622 const struct GameDriver *gdrv;
623
624 gdrv = drv;
625 do
626 {
627 err = load_datafile_text (gdrv, buffer, bufsize,
628 hist_idx, DATAFILE_TAG_BIO);
629 gdrv = gdrv->clone_of;
630 } while (err && gdrv);
631
632 if (err) history = 0;
633 }
634 ParseClose ();
635 }
636
637 if(!mameinfo_filename)
638 mameinfo_filename = "mameinfo.dat";
639
640 /* try to open mameinfo datafile */
641 if (ParseOpen (mameinfo_filename))
642 {
643 /* create index if necessary */
644 if (mame_idx)
645 mameinfo = 1;
646 else
647 mameinfo = (index_datafile (&mame_idx) != 0);
648
649 /* load informational text (append) */
650 if (mame_idx)
651 {
652 int len = strlen (buffer);
653 const struct GameDriver *gdrv;
654
655 gdrv = drv;
656 do
657 {
658 err = load_datafile_text (gdrv, buffer+len, bufsize-len,
659 mame_idx, DATAFILE_TAG_MAME);
660 gdrv = gdrv->clone_of;
661 } while (err && gdrv);
662
663 if (err) mameinfo = 0;
664 }
665 ParseClose ();
666 }
667
668 return (history == 0 && mameinfo == 0);
669 }
670