1 /*	hiscore.c
2 **	generalized high score save/restore support
3 */
4 
5 #include "driver.h"
6 #include "hiscore.h"
7 
8 #define MAX_CONFIG_LINE_SIZE 48
9 
10 #define VERBOSE 0
11 
12 #if VERBOSE
13 #define LOG(x)	logerror x
14 #else
15 #define LOG(x)
16 #endif
17 
18 char *db_filename = "hiscore.dat"; /* high score definition file */
19 
20 struct mem_range
21 {
22 	UINT32 cpu, addr, num_bytes, start_value, end_value;
23 	struct mem_range *next;
24 };
25 
26 static struct
27 {
28 	int hiscores_have_been_loaded;
29 	struct mem_range *mem_range;
30 } state;
31 
32 /*****************************************************************************/
33 
34 #define MEMORY_READ(index,offset) \
35     ((*cpuintf[Machine->drv->cpu[index].cpu_type & ~CPU_FLAGS_MASK]. \
36     memory_read)(offset))
37 #define MEMORY_WRITE(index,offset,data) \
38     ((*cpuintf[Machine->drv->cpu[index].cpu_type & ~CPU_FLAGS_MASK]. \
39     memory_write)(offset,data))
40 
computer_writemem_byte(int cpu,int addr,int value)41 void computer_writemem_byte(int cpu, int addr, int value)
42 {
43     int oldcpu = cpu_getactivecpu();
44     memorycontextswap(cpu);
45     MEMORY_WRITE(cpu, addr, value);
46     if (oldcpu != cpu)
47 		memorycontextswap(oldcpu);
48 }
49 
computer_readmem_byte(int cpu,int addr)50 int computer_readmem_byte(int cpu, int addr)
51 {
52     int oldcpu = cpu_getactivecpu(), result;
53     memorycontextswap(cpu);
54     result = MEMORY_READ(cpu, addr);
55     if (oldcpu != cpu)
56     	memorycontextswap(oldcpu);
57     return result;
58 }
59 
60 /*****************************************************************************/
61 
copy_to_memory(int cpu,int addr,const UINT8 * source,int num_bytes)62 static void copy_to_memory (int cpu, int addr, const UINT8 *source, int num_bytes)
63 {
64 	int i;
65 	for (i=0; i<num_bytes; i++)
66 	{
67 		computer_writemem_byte (cpu, addr+i, source[i]);
68 	}
69 }
70 
copy_from_memory(int cpu,int addr,UINT8 * dest,int num_bytes)71 static void copy_from_memory (int cpu, int addr, UINT8 *dest, int num_bytes)
72 {
73 	int i;
74 	for (i=0; i<num_bytes; i++)
75 	{
76 		dest[i] = computer_readmem_byte (cpu, addr+i);
77 	}
78 }
79 
80 /*****************************************************************************/
81 
82 /*	hexstr2num extracts and returns the value of a hexadecimal field from the
83 	character buffer pointed to by pString.
84 
85 	When hexstr2num returns, *pString points to the character following
86 	the first non-hexadecimal digit, or NULL if an end-of-string marker
87 	(0x00) is encountered.
88 
89 */
hexstr2num(const char ** pString)90 static UINT32 hexstr2num (const char **pString)
91 {
92 	const char *string = *pString;
93 	UINT32 result = 0;
94 	if (string)
95 	{
96 		for(;;)
97 		{
98 			char c = *string++;
99 			int digit;
100 
101 			if (c>='0' && c<='9')
102 			{
103 				digit = c-'0';
104 			}
105 			else if (c>='a' && c<='f')
106 			{
107 				digit = 10+c-'a';
108 			}
109 			else if (c>='A' && c<='F')
110 			{
111 				digit = 10+c-'A';
112 			}
113 			else
114 			{
115 				/* not a hexadecimal digit */
116 				/* safety check for premature EOL */
117 				if (!c) string = NULL;
118 				break;
119 			}
120 			result = result*16 + digit;
121 		}
122 		*pString = string;
123 	}
124 	return result;
125 }
126 
127 /*	given a line in the hiscore.dat file, determine if it encodes a
128 	memory range (or a game name).
129 	For now we assume that CPU number is always a decimal digit, and
130 	that no game name starts with a decimal digit.
131 */
is_mem_range(const char * pBuf)132 static int is_mem_range (const char *pBuf)
133 {
134 	char c;
135 	for(;;)
136 	{
137 		c = *pBuf++;
138 		if (c == 0) return 0; /* premature EOL */
139 		if (c == ':') break;
140 	}
141 	c = *pBuf; /* character following first ':' */
142 
143 	return	(c>='0' && c<='9') ||
144 			(c>='a' && c<='f') ||
145 			(c>='A' && c<='F');
146 }
147 
148 /*	matching_game_name is used to skip over lines until we find <gamename>: */
matching_game_name(const char * pBuf,const char * name)149 static int matching_game_name (const char *pBuf, const char *name)
150 {
151 	while (*name)
152 	{
153 		if (*name++ != *pBuf++) return 0;
154 	}
155 	return (*pBuf == ':');
156 }
157 
158 /*****************************************************************************/
159 
160 /* safe_to_load checks the start and end values of each memory range */
safe_to_load(void)161 static int safe_to_load (void)
162 {
163 	struct mem_range *mem_range = (struct mem_range *)state.mem_range;
164 	while (mem_range)
165 	{
166 		if (computer_readmem_byte (mem_range->cpu, mem_range->addr) !=
167 			mem_range->start_value)
168 		{
169 			return 0;
170 		}
171 		if (computer_readmem_byte (mem_range->cpu, mem_range->addr + mem_range->num_bytes - 1) !=
172 			mem_range->end_value)
173 		{
174 			return 0;
175 		}
176 		mem_range = mem_range->next;
177 	}
178 	return 1;
179 }
180 
181 /* hs_free disposes of the mem_range linked list */
hs_free(void)182 static void hs_free (void)
183 {
184 	struct mem_range *mem_range = state.mem_range;
185 	while (mem_range)
186 	{
187 		struct mem_range *next = mem_range->next;
188 		free(mem_range);
189 		mem_range = next;
190 	}
191 	state.mem_range = NULL;
192 }
193 
hs_load(void)194 static void hs_load (void)
195 {
196 	void *f = osd_fopen (Machine->gamedrv->name, 0, OSD_FILETYPE_HIGHSCORE, 0);
197 	state.hiscores_have_been_loaded = 1;
198 	LOG(("hs_load\n"));
199 	if (f)
200 	{
201 		struct mem_range *mem_range = state.mem_range;
202 		LOG(("loading...\n"));
203 		while (mem_range)
204 		{
205 			UINT8 *data = (UINT8*)malloc(mem_range->num_bytes);
206 			if (data)
207 			{
208 				/*	this buffer will almost certainly be small
209 					enough to be dynamically allocated, but let's
210 					avoid memory trashing just in case
211 				*/
212 				osd_fread (f, data, mem_range->num_bytes);
213 				copy_to_memory (mem_range->cpu, mem_range->addr, data, mem_range->num_bytes);
214 				free(data);
215 			}
216 			mem_range = mem_range->next;
217 		}
218 		osd_fclose (f);
219 	}
220 }
221 
hs_save(void)222 static void hs_save (void)
223 {
224 	void *f = osd_fopen (Machine->gamedrv->name, 0, OSD_FILETYPE_HIGHSCORE, 1);
225 	LOG(("hs_save\n"));
226 	if (f)
227 	{
228 		struct mem_range *mem_range = state.mem_range;
229 		LOG(("saving...\n"));
230 		while (mem_range)
231 		{
232 			UINT8 *data = (UINT8*)malloc(mem_range->num_bytes);
233 			if (data)
234 			{
235 				/*	this buffer will almost certainly be small
236 					enough to be dynamically allocated, but let's
237 					avoid memory trashing just in case
238 				*/
239 				copy_from_memory (mem_range->cpu, mem_range->addr, data, mem_range->num_bytes);
240 				osd_fwrite(f, data, mem_range->num_bytes);
241 			}
242 			mem_range = mem_range->next;
243 		}
244 		osd_fclose(f);
245 	}
246 }
247 
248 /*****************************************************************************/
249 /* public API */
250 
251 /* call hs_open once after loading a game */
hs_open(const char * name)252 void hs_open (const char *name)
253 {
254 	void *f = osd_fopen (NULL, db_filename, OSD_FILETYPE_HIGHSCORE_DB, 0);
255 	state.mem_range = NULL;
256 
257 	LOG(("hs_open: '%s'\n", name));
258 
259 	if (f)
260 	{
261 		char buffer[MAX_CONFIG_LINE_SIZE];
262 		enum { FIND_NAME, FIND_DATA, FETCH_DATA } mode;
263 		mode = FIND_NAME;
264 
265 		while (osd_fgets (buffer, MAX_CONFIG_LINE_SIZE, f))
266 		{
267 			if (mode==FIND_NAME)
268 			{
269 				if (matching_game_name (buffer, name))
270 				{
271 					mode = FIND_DATA;
272 					LOG(("hs config found!\n"));
273 				}
274 			}
275 			else if (is_mem_range (buffer))
276 			{
277 				const char *pBuf = buffer;
278 				struct mem_range *mem_range = (struct mem_range*)malloc(sizeof(struct mem_range));
279 				if (mem_range)
280 				{
281 					mem_range->cpu = hexstr2num (&pBuf);
282 					mem_range->addr = hexstr2num (&pBuf);
283 					mem_range->num_bytes = hexstr2num (&pBuf);
284 					mem_range->start_value = hexstr2num (&pBuf);
285 					mem_range->end_value = hexstr2num (&pBuf);
286 
287 					mem_range->next = NULL;
288 					{
289 						struct mem_range *last = state.mem_range;
290 						while (last && last->next) last = last->next;
291 						if (last == NULL)
292 						{
293 							state.mem_range = mem_range;
294 						}
295 						else
296 						{
297 							last->next = mem_range;
298 						}
299 					}
300 
301 					mode = FETCH_DATA;
302 				}
303 				else
304 				{
305 					hs_free();
306 					break;
307 				}
308 			}
309 			else
310 			{
311 				/* line is a game name */
312 				if (mode == FETCH_DATA) break;
313 			}
314 		}
315 		osd_fclose (f);
316 	}
317 }
318 
319 /* call hs_init when emulation starts, and when the game is reset */
hs_init(void)320 void hs_init (void)
321 {
322 	struct mem_range *mem_range = state.mem_range;
323 	state.hiscores_have_been_loaded = 0;
324 
325 	while (mem_range)
326 	{
327 		computer_writemem_byte(
328 			mem_range->cpu,
329 			mem_range->addr,
330 			~mem_range->start_value
331 		);
332 
333 		computer_writemem_byte(
334 			mem_range->cpu,
335 			mem_range->addr + mem_range->num_bytes-1,
336 			~mem_range->end_value
337 		);
338 		mem_range = mem_range->next;
339 	}
340 }
341 
342 /* call hs_update periodically (i.e. once per frame) */
hs_update(void)343 void hs_update (void)
344 {
345 	if (state.mem_range)
346 	{
347 		if (!state.hiscores_have_been_loaded)
348 		{
349 			if (safe_to_load()) hs_load();
350 		}
351 	}
352 }
353 
354 /* call hs_close when done playing game */
hs_close(void)355 void hs_close (void)
356 {
357 	if (state.hiscores_have_been_loaded) hs_save();
358 	hs_free();
359 }
360