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