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