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