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