1 /*
2  * resources.c - Resource (setting) handling for VICE.
3  *
4  * Written by
5  *  Ettore Perazzoli <ettore@comm2000.it>
6  *  Andreas Boose <viceteam@t-online.de>
7  *
8  * This file is part of VICE, the Versatile Commodore Emulator.
9  * See README for copyright notice.
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24  *  02111-1307  USA.
25  *
26  */
27 
28 /* This implements simple facilities to handle the resources and command-line
29    options.  All the resources for the emulators can be stored in a single
30    file, and they are separated by an `emulator identifier', i.e. the machine
31    name between brackets (e.g. ``[C64]'').  All the resources are stored in
32    the form ``ResourceName=ResourceValue'', and separated by newline
33    characters.  Leading and trailing spaces are removed from the
34    ResourceValue unless it is put between quotes (").  */
35 
36 #include "vice.h"
37 
38 /* #define VICE_DEBUG_RESOURCES */
39 
40 #include <stdarg.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <ctype.h>
45 
46 #ifdef HAVE_STRINGS_H
47 #include <strings.h>
48 #endif
49 
50 #include "archdep.h"
51 #include "cartridge.h"
52 #include "ioutil.h"
53 #include "lib.h"
54 #include "log.h"
55 #include "network.h"
56 #include "resources.h"
57 #include "util.h"
58 #include "vice-event.h"
59 
60 #ifdef VICE_DEBUG_RESOURCES
61 #define DBG(x)  printf x
62 #else
63 #define DBG(x)
64 #endif
65 
66 
67 /** \brief  Initial size of the array holding resources
68  */
69 #define NUM_ALLOCATED_RESOURCES_INIT    512
70 
71 
72 typedef struct resource_ram_s {
73     /* Resource name.  */
74     char *name;
75 
76     /* Type of resource.  */
77     resource_type_t type;
78 
79     /* Factory default value.  */
80     resource_value_t factory_value;
81 
82     /* Is the resource important for history recording or netplay? */
83     resource_event_relevant_t event_relevant;
84 
85     /* Value that is needed for correct history recording and netplay.  */
86     resource_value_t *event_strict_value;
87 
88     /* Pointer to the value.  This is only used for *reading* it.  To change
89        it, use `set_func'.  */
90     resource_value_t *value_ptr;
91 
92     /* Function to call to set the integer value.  */
93     resource_set_func_int_t *set_func_int;
94 
95     /* Function to call to set the string value.  */
96     resource_set_func_string_t *set_func_string;
97 
98     /* Extra parameter to pass to `set_func'.  */
99     void *param;
100 
101     /* callback function vector chain */
102     struct resource_callback_desc_s *callback;
103 
104     /* number of next entry in hash collision list */
105     int hash_next;
106 } resource_ram_t;
107 
108 
109 /* the type of the callback vector chain */
110 typedef struct resource_callback_desc_s {
111     resource_callback_func_t *func;
112     void *param;
113     struct resource_callback_desc_s *next;
114 } resource_callback_desc_t;
115 
116 
117 static unsigned int num_resources;
118 static unsigned int num_allocated_resources;
119 static resource_ram_t *resources;
120 static char *machine_id = NULL;
121 
122 static void write_resource_item(FILE *f, int num);
123 static char *string_resource_item(int num, const char *delim);
124 
125 /* use a hash table with 1024 entries */
126 static const unsigned int logHashSize = 10;
127 
128 static int *hashTable = NULL;
129 
130 static resource_callback_desc_t *resource_modified_callback = NULL;
131 
132 /* calculate the hash key */
resources_calc_hash_key(const char * name)133 static unsigned int resources_calc_hash_key(const char *name)
134 {
135     unsigned int key, i, shift;
136 
137     DBG(("resources_calc_hash_key: '%s'\n", name ? name : "<empty/null>"));
138 
139     key = 0; shift = 0;
140     for (i = 0; name[i] != '\0'; i++) {
141         /* resources are case-insensitive */
142         unsigned int sym = (unsigned int)tolower((int)name[i]);
143 
144         if (shift >= logHashSize) {
145             shift = 0;
146         }
147 
148         key ^= (sym << shift);
149         if (shift + 8 > logHashSize) {
150             key ^= (((unsigned int)sym) >> (logHashSize - shift));
151         }
152         shift++;
153     }
154     return (key & ((1 << logHashSize) - 1));
155 }
156 
157 
158 /* add a new callback function at the head of the vector chain */
resources_add_callback(resource_callback_desc_t ** where,resource_callback_func_t * callback,void * param)159 static void resources_add_callback(resource_callback_desc_t **where,
160                                    resource_callback_func_t *callback,
161                                    void *param)
162 {
163     if (callback != NULL) {
164         resource_callback_desc_t *cbd;
165 
166         cbd = lib_malloc(sizeof(resource_callback_desc_t));
167         cbd->func = callback;
168         cbd->param = param;
169         cbd->next = *where;
170         *where = cbd;
171     }
172 }
173 
174 
175 /* execute a callback vector chain */
resources_exec_callback_chain(const resource_callback_desc_t * callbacks,const char * name)176 static void resources_exec_callback_chain(const resource_callback_desc_t
177                                           *callbacks, const char *name)
178 {
179     const resource_callback_desc_t *cbd = callbacks;
180 
181     while (cbd != NULL) {
182         (*cbd->func)(name, cbd->param);
183         cbd = cbd->next;
184     }
185 }
186 
187 
188 /* issue callbacks for a modified resource */
resources_issue_callback(resource_ram_t * res,int global_callback)189 static void resources_issue_callback(resource_ram_t *res, int global_callback)
190 {
191     if (res->callback != NULL) {
192         resources_exec_callback_chain(res->callback, res->name);
193     }
194 
195     if ((global_callback != 0) && (resource_modified_callback != NULL)) {
196         resources_exec_callback_chain(resource_modified_callback, res->name);
197     }
198 }
199 
200 
201 #if 0
202 /* for debugging (hash collisions, hash chains, ...) */
203 static void resources_check_hash_table(FILE *f)
204 {
205     int i, entries;
206 
207     for (i = 0, entries = 0; i < (1 << logHashSize); i++) {
208         if (hashTable[i] >= 0) {
209             int next;
210 
211             fprintf(f, "%d: %s", i, resources[hashTable[i]].name);
212             next = resources[hashTable[i]].hash_next;
213             while (next >= 0) {
214                 fprintf(f, " -> %s", resources[next].name);
215                 next = resources[next].hash_next;
216             }
217             fprintf(f, "\n");
218             entries++;
219         }
220     }
221     fprintf(f, "NUM %d, ENTIES %d\n", num_resources, entries);
222 }
223 #endif
224 
lookup(const char * name)225 static resource_ram_t *lookup(const char *name)
226 {
227     resource_ram_t *res;
228     unsigned int hashkey;
229 
230     if (name == NULL) {
231         return NULL;
232     }
233     hashkey = resources_calc_hash_key(name);
234     res = (hashTable[hashkey] >= 0) ? resources + hashTable[hashkey] : NULL;
235     while (res != NULL) {
236         if (strcasecmp(res->name, name) == 0) {
237             return res;
238         }
239         res = (res->hash_next >= 0) ? resources + res->hash_next : NULL;
240     }
241     return NULL;
242 }
243 
244 /* Configuration filename set via -config */
245 char *vice_config_file = NULL;
246 
247 /* ------------------------------------------------------------------------- */
248 /* register an array(!) of integer resources */
resources_register_int(const resource_int_t * r)249 int resources_register_int(const resource_int_t *r)
250 {
251     const resource_int_t *sp;
252     resource_ram_t *dp;
253 
254     DBG(("resources_register_int name:'%s'\n", r->name ? r->name : "<empty/null>"));
255 
256     sp = r;
257     dp = resources + num_resources;
258     while (sp->name != NULL) {
259         unsigned int hashkey;
260 
261         if (sp->value_ptr == NULL || sp->set_func == NULL) {
262             archdep_startup_log_error(
263                 "Inconsistent resource declaration '%s'.\n", sp->name);
264             return -1;
265         }
266 
267         if (lookup(sp->name)) {
268             archdep_startup_log_error(
269                 "Duplicated resource declaration '%s'.\n", sp->name);
270             return -1;
271         }
272 
273         if (num_allocated_resources <= num_resources) {
274             num_allocated_resources *= 2;
275             resources = lib_realloc(resources, num_allocated_resources
276                                     * sizeof(resource_ram_t));
277             dp = resources + num_resources;
278         }
279 
280         dp->name = lib_strdup(sp->name);
281         dp->type = RES_INTEGER;
282         dp->factory_value = uint_to_void_ptr(sp->factory_value);
283         dp->value_ptr = (void *)(sp->value_ptr);
284         dp->event_relevant = sp->event_relevant;
285         dp->event_strict_value = sp->event_strict_value;
286         dp->set_func_int = sp->set_func;
287         dp->param = sp->param;
288         dp->callback = NULL;
289 
290         hashkey = resources_calc_hash_key(sp->name);
291         dp->hash_next = hashTable[hashkey];
292         hashTable[hashkey] = (int)(dp - resources);
293 
294         num_resources++;
295         sp++;
296         dp++;
297     }
298 
299     return 0;
300 }
301 
resources_register_string(const resource_string_t * r)302 int resources_register_string(const resource_string_t *r)
303 {
304     const resource_string_t *sp;
305     resource_ram_t *dp;
306 
307     DBG(("resources_register_string name:'%s'\n", r->name ? r->name : "<empty/null>"));
308 
309     sp = r;
310     dp = resources + num_resources;
311     while (sp->name != NULL) {
312         unsigned int hashkey;
313 
314         if (sp->factory_value == NULL
315             || sp->value_ptr == NULL || sp->set_func == NULL) {
316             archdep_startup_log_error(
317                 "Inconsistent resource declaration '%s'.\n", sp->name);
318             return -1;
319         }
320 
321         if (lookup(sp->name)) {
322             archdep_startup_log_error(
323                 "Duplicated resource declaration '%s'.\n", sp->name);
324             return -1;
325         }
326 
327         if (num_allocated_resources <= num_resources) {
328             num_allocated_resources *= 2;
329             resources = lib_realloc(resources, num_allocated_resources
330                                     * sizeof(resource_ram_t));
331             dp = resources + num_resources;
332         }
333 
334         dp->name = lib_strdup(sp->name);
335         dp->type = RES_STRING;
336         dp->factory_value = (resource_value_t)(sp->factory_value);
337         dp->value_ptr = (void *)(sp->value_ptr);
338         dp->event_relevant = sp->event_relevant;
339         dp->event_strict_value = sp->event_strict_value;
340         dp->set_func_string = sp->set_func;
341         dp->param = sp->param;
342         dp->callback = NULL;
343 
344         hashkey = resources_calc_hash_key(sp->name);
345         dp->hash_next = hashTable[hashkey];
346         hashTable[hashkey] = (int)(dp - resources);
347 
348         num_resources++, sp++, dp++;
349     }
350 
351     return 0;
352 }
353 
354 
resources_free(void)355 static void resources_free(void)
356 {
357     unsigned int i;
358 
359     for (i = 0; i < num_resources; i++) {
360         lib_free((resources + i)->name);
361     }
362 }
363 
364 
365 /** \brief  Shutdown resources
366  */
resources_shutdown(void)367 void resources_shutdown(void)
368 {
369     resources_free();
370 
371     lib_free(resources);
372     lib_free(hashTable);
373     lib_free(machine_id);
374     lib_free(vice_config_file);
375 }
376 
resources_query_type(const char * name)377 resource_type_t resources_query_type(const char *name)
378 {
379     resource_ram_t *res;
380 
381     if ((res = lookup(name)) != NULL) {
382         return res->type;
383     } else {
384         return (resource_type_t)-1;
385     }
386 }
387 
resources_write_item_to_file(FILE * fp,const char * name)388 int resources_write_item_to_file(FILE *fp, const char *name)
389 {
390     resource_ram_t *res = lookup(name);
391 
392     if (res != NULL) {
393         write_resource_item(fp, (int)(res - resources));
394         return 0;
395     }
396     log_warning(LOG_DEFAULT, "Trying to save unknown resource '%s'", name);
397 
398     return -1;
399 }
400 
resources_write_item_to_string(const char * name,const char * delim)401 char *resources_write_item_to_string(const char *name, const char *delim)
402 {
403     resource_ram_t *res = lookup(name);
404 
405     if (res != NULL) {
406         return string_resource_item((int)(res - resources), delim);
407     }
408 
409     log_warning(LOG_DEFAULT, "Trying to save unknown resource '%s'", name);
410 
411     return NULL;
412 }
413 
resource_create_event_data(char ** event_data,int * data_size,resource_ram_t * r,resource_value_t value)414 static void resource_create_event_data(char **event_data, int *data_size,
415                                        resource_ram_t *r,
416                                        resource_value_t value)
417 {
418     int name_size;
419     const char *name = r->name;
420 
421     name_size = (int)strlen(name) + 1;
422 
423     if (r->type == RES_INTEGER) {
424         *data_size = name_size + sizeof(uint32_t);
425     } else {
426         *data_size = name_size + (int)strlen((char *)value) + 1;
427     }
428 
429     *event_data = lib_malloc(*data_size);
430     strcpy(*event_data, name);
431 
432     if (r->type == RES_INTEGER) {
433         *(uint32_t *)(*event_data + name_size) = vice_ptr_to_uint(value);
434     } else {
435         strcpy(*event_data + name_size, (char *)value);
436     }
437 }
438 
resource_record_event(resource_ram_t * r,resource_value_t value)439 static void resource_record_event(resource_ram_t *r,
440                                   resource_value_t value)
441 {
442     char *event_data;
443     int data_size;
444 
445     resource_create_event_data(&event_data, &data_size, r, value);
446 
447     network_event_record(EVENT_RESOURCE, event_data, data_size);
448 
449     lib_free(event_data);
450 }
451 
452 /* ------------------------------------------------------------------------- */
453 
454 
455 /* Total resources registered per emu, using Gtk3 (2020-02-24)
456  *
457  * x128         509
458  * x64sc        471
459  * x64          469
460  * xscpu64      445
461  * xvic         364
462  * xplus4       321
463  * x64dtv       309
464  * xpet         300
465  * xcbm2        284
466  * xcbm5x0      272
467  * vsid         63
468  */
469 
470 
471 /** \brief  Initialize resources module
472  *
473  * Allocated memory for resource objects and the hash table.
474  *
475  * \param[in]   machine machine name
476  *
477  * \return  0
478  */
resources_init(const char * machine)479 int resources_init(const char *machine)
480 {
481     unsigned int i;
482 
483     machine_id = lib_strdup(machine);
484     num_allocated_resources = NUM_ALLOCATED_RESOURCES_INIT;
485     num_resources = 0;
486     resources = lib_malloc(num_allocated_resources * sizeof(resource_ram_t));
487 
488     /* hash table maps hash keys to index in resources array rather than
489        pointers into the array because the array may be reallocated. */
490     hashTable = lib_malloc((1 << logHashSize) * sizeof(int));
491 
492     for (i = 0; i < (unsigned int)(1 << logHashSize); i++) {
493         hashTable[i] = -1;
494     }
495 
496     return 0;
497 }
498 
resources_set_value_internal(resource_ram_t * r,resource_value_t value)499 static int resources_set_value_internal(resource_ram_t *r,
500                                         resource_value_t value)
501 {
502     int status = 0;
503 
504     switch (r->type) {
505         case RES_INTEGER:
506             status = (*r->set_func_int)(vice_ptr_to_int(value), r->param);
507             break;
508         case RES_STRING:
509             status = (*r->set_func_string)((const char *)value, r->param);
510             break;
511     }
512 
513     if (status != 0) {
514         resources_issue_callback(r, 1);
515     }
516 
517     return status;
518 }
519 
resources_set_value(const char * name,resource_value_t value)520 int resources_set_value(const char *name, resource_value_t value)
521 {
522     resource_ram_t *r = lookup(name);
523 
524     if (r == NULL) {
525         log_warning(LOG_DEFAULT,
526                     "Trying to assign value to unknown "
527                     "resource `%s'.", name);
528         return -1;
529     }
530 
531     if (r->event_relevant == RES_EVENT_STRICT
532         && network_get_mode() != NETWORK_IDLE) {
533         return -2;
534     }
535 
536     if (r->event_relevant == RES_EVENT_SAME && network_connected()) {
537         resource_record_event(r, value);
538         return 0;
539     }
540 
541     return resources_set_value_internal(r, value);
542 }
543 
resources_set_internal_int(resource_ram_t * r,int value)544 static int resources_set_internal_int(resource_ram_t *r, int value)
545 {
546     int status = 0;
547 
548     switch (r->type) {
549         case RES_INTEGER:
550             status = (*r->set_func_int)(value, r->param);
551             break;
552         default:
553             return -1;
554     }
555 
556     if (status != 0) {
557         resources_issue_callback(r, 1);
558     }
559 
560     return status;
561 }
562 
resources_set_internal_string(resource_ram_t * r,const char * value)563 static int resources_set_internal_string(resource_ram_t *r,
564                                          const char *value)
565 {
566     int status = 0;
567 
568     switch (r->type) {
569         case RES_STRING:
570             status = (*r->set_func_string)(value, r->param);
571             break;
572         default:
573             return -1;
574     }
575 
576     if (status != 0) {
577         resources_issue_callback(r, 1);
578     }
579 
580     return status;
581 }
582 
resources_set_int(const char * name,int value)583 int resources_set_int(const char *name, int value)
584 {
585     resource_ram_t *r = lookup(name);
586 
587     if (r == NULL) {
588         log_warning(LOG_DEFAULT,
589                     "Trying to assign value to unknown "
590                     "resource `%s'.", name);
591         return -1;
592     }
593 
594     if (r->event_relevant == RES_EVENT_STRICT && network_get_mode() != NETWORK_IDLE) {
595         return -2;
596     }
597 
598     if (r->event_relevant == RES_EVENT_SAME && network_connected()) {
599         resource_record_event(r, uint_to_void_ptr(value));
600         return 0;
601     }
602 
603     return resources_set_internal_int(r, value);
604 }
605 
resources_set_string(const char * name,const char * value)606 int resources_set_string(const char *name, const char *value)
607 {
608     resource_ram_t *r = lookup(name);
609 
610     if (r == NULL) {
611         log_warning(LOG_DEFAULT,
612                     "Trying to assign value to unknown "
613                     "resource `%s'.", name);
614         return -1;
615     }
616 
617     if (r->event_relevant == RES_EVENT_STRICT && network_get_mode() != NETWORK_IDLE) {
618         return -2;
619     }
620 
621     if (r->event_relevant == RES_EVENT_SAME && network_connected()) {
622         resource_record_event(r, (resource_value_t)value);
623         return 0;
624     }
625 
626     return resources_set_internal_string(r, value);
627 }
628 
resources_set_value_event(void * data,int size)629 void resources_set_value_event(void *data, int size)
630 {
631     char *name;
632     char *valueptr;
633     resource_ram_t *r;
634 
635     name = data;
636     valueptr = name + strlen(name) + 1;
637     r = lookup(name);
638     if (r->type == RES_INTEGER) {
639         resources_set_value_internal(r, (resource_value_t) uint_to_void_ptr(*(uint32_t *)valueptr));
640     } else {
641         resources_set_value_internal(r, (resource_value_t)valueptr);
642     }
643 }
644 
resources_set_int_sprintf(const char * name,int value,...)645 int resources_set_int_sprintf(const char *name, int value, ...)
646 {
647     va_list args;
648     char *resname;
649     int result;
650 
651     va_start(args, value);
652     resname = lib_mvsprintf(name, args);
653     va_end(args);
654 
655     result = resources_set_int(resname, value);
656     lib_free(resname);
657 
658     return result;
659 }
660 
resources_set_string_sprintf(const char * name,const char * value,...)661 int resources_set_string_sprintf(const char *name, const char *value, ...)
662 {
663     va_list args;
664     char *resname;
665     int result;
666 
667     va_start(args, value);
668     resname = lib_mvsprintf(name, args);
669     va_end(args);
670 
671     result = resources_set_string(resname, value);
672     lib_free(resname);
673 
674     return result;
675 }
676 
resources_set_value_string(const char * name,const char * value)677 int resources_set_value_string(const char *name, const char *value)
678 {
679     resource_ram_t *r = lookup(name);
680     int status;
681 
682     if (r == NULL) {
683         log_warning(LOG_DEFAULT,
684                     "Trying to assign value to unknown "
685                     "resource `%s'.", name);
686         return -1;
687     }
688 
689     switch (r->type) {
690         case RES_INTEGER:
691             {
692                 char *endptr;
693                 int int_value;
694 
695                 int_value = (int)strtol(value, &endptr, 0);
696 
697                 if (*endptr == '\0') {
698                     status = (*r->set_func_int)(int_value, r->param);
699                 } else {
700                     status = -1;
701                 }
702             }
703             break;
704         case RES_STRING:
705             status = (*r->set_func_string)(value, r->param);
706             break;
707         default:
708             log_warning(LOG_DEFAULT, "Unknown resource type for `%s'", name);
709             status = -1;
710             break;
711     }
712 
713     if (status != 0) {
714         resources_issue_callback(r, 1);
715     }
716 
717     return status;
718 }
719 
resources_get_value(const char * name,void * value_return)720 int resources_get_value(const char *name, void *value_return)
721 {
722     resource_ram_t *r = lookup(name);
723 
724     if (r == NULL) {
725         log_warning(LOG_DEFAULT,
726                     "Trying to read value from unknown "
727                     "resource `%s'.", name);
728         return -1;
729     }
730 
731     switch (r->type) {
732         case RES_INTEGER:
733             *(int *)value_return = *(int *)r->value_ptr;
734             break;
735         case RES_STRING:
736             *(char **)value_return = *(char **)r->value_ptr;
737             break;
738         default:
739             log_warning(LOG_DEFAULT, "Unknown resource type for `%s'", name);
740             return -1;
741     }
742 
743     return 0;
744 }
745 
746 
747 /** \brief  Get value for resource \a name and store in \a value_return
748  *
749  * If the resource is unknown, the return value is set to 0.
750  *
751  * \param[in]   name            resource name
752  * \param[out]  value_return    resource value target
753  *
754  * \return  0 on succes, -1 on failure
755  */
resources_get_int(const char * name,int * value_return)756 int resources_get_int(const char *name, int *value_return)
757 {
758     resource_ram_t *r = lookup(name);
759 
760     /* set some sane value */
761     *value_return = 0;
762 
763     if (r == NULL) {
764         log_warning(LOG_DEFAULT,
765                     "Trying to read value from unknown "
766                     "resource `%s'.", name);
767         return -1;
768     }
769 
770     switch (r->type) {
771         case RES_INTEGER:
772             *value_return = *(int *)r->value_ptr;
773             break;
774         default:
775             log_warning(LOG_DEFAULT, "Unknown resource type for `%s'", name);
776             return -1;
777     }
778 
779     return 0;
780 }
781 
782 
783 /** \brief  Get string resource \a name and store in \a value_return
784  *
785  * If the resource \a name is unknown, \a value_return is set to NULL.
786  *
787  * \param[in]   name            resource name
788  * \param[out]  value_return    resource value target
789  *
790  * \return  0 on success, -1 on failure
791  */
resources_get_string(const char * name,const char ** value_return)792 int resources_get_string(const char *name, const char **value_return)
793 {
794     resource_ram_t *r = lookup(name);
795 
796     /* don't return an unitialized value, NULL is probably a good choice to
797      * trace bugs
798      */
799     *value_return = NULL;
800 
801     if (r == NULL) {
802         log_warning(LOG_DEFAULT,
803                     "Trying to read value from unknown "
804                     "resource `%s'.", name);
805         return -1;
806     }
807 
808     switch (r->type) {
809         case RES_STRING:
810             *value_return = *(const char **)r->value_ptr;
811             break;
812         default:
813             log_warning(LOG_DEFAULT, "Unknown resource type for `%s'", name);
814             return -1;
815     }
816 
817     return 0;
818 }
819 
resources_get_int_sprintf(const char * name,int * value_return,...)820 int resources_get_int_sprintf(const char *name, int *value_return, ...)
821 {
822     va_list args;
823     char *resname;
824     int result;
825 
826     va_start(args, value_return);
827     resname = lib_mvsprintf(name, args);
828     va_end(args);
829 
830     result = resources_get_int(resname, value_return);
831     lib_free(resname);
832 
833     return result;
834 }
835 
resources_get_string_sprintf(const char * name,const char ** value_return,...)836 int resources_get_string_sprintf(const char *name, const char **value_return,
837                                  ...)
838 {
839     va_list args;
840     char *resname;
841     int result;
842 
843     va_start(args, value_return);
844     resname = lib_mvsprintf(name, args);
845     va_end(args);
846 
847     result = resources_get_string(resname, value_return);
848     lib_free(resname);
849 
850     return result;
851 }
852 
resources_set_default_int(const char * name,int value)853 int resources_set_default_int(const char *name, int value)
854 {
855     resource_ram_t *r = lookup(name);
856 
857     if (r == NULL) {
858         log_warning(LOG_DEFAULT,
859                     "Trying to assign default to unknown "
860                     "resource `%s'.", name);
861         return -1;
862     }
863 
864     r->factory_value = uint_to_void_ptr(value);
865     return 0;
866 }
867 
resources_set_default_string(const char * name,char * value)868 int resources_set_default_string(const char *name, char *value)
869 {
870     resource_ram_t *r = lookup(name);
871 
872     if (r == NULL) {
873         log_warning(LOG_DEFAULT,
874                     "Trying to assign default to unknown "
875                     "resource `%s'.", name);
876         return -1;
877     }
878     /* since these pointers are usually static/not allocated, we just
879        assign it here and don't free() as one might expect */
880     r->factory_value = value;
881     return 0;
882 }
883 
resources_get_default_value(const char * name,void * value_return)884 int resources_get_default_value(const char *name, void *value_return)
885 {
886     resource_ram_t *r = lookup(name);
887 
888     if (r == NULL) {
889         log_warning(LOG_DEFAULT,
890                     "Trying to read value from unknown "
891                     "resource `%s'.", name);
892         return -1;
893     }
894 
895     switch (r->type) {
896         case RES_INTEGER:
897             *(int *)value_return = vice_ptr_to_int(r->factory_value);
898             break;
899         case RES_STRING:
900             *(char **)value_return = (char *)(r->factory_value);
901             break;
902         default:
903             log_warning(LOG_DEFAULT, "Unknown resource type for `%s'", name);
904             return -1;
905     }
906 
907     return 0;
908 }
909 
910 /* FIXME: make event safe */
resources_set_defaults(void)911 int resources_set_defaults(void)
912 {
913     unsigned int i;
914 
915     /* the cartridge system uses internal state variables so the default cartridge
916        can be unset without changing the attached cartridge and/or attach another
917        cartridge without changing the default. to completely restore the default,
918        which is no default cartridge, and no currently attached cartridge, call
919        the respective functions of the cartridge system here */
920     cartridge_unset_default();
921     cartridge_detach_image(-1);
922 
923     for (i = 0; i < num_resources; i++) {
924         switch (resources[i].type) {
925             case RES_INTEGER:
926                 if ((*resources[i].set_func_int)(vice_ptr_to_int(resources[i].factory_value),
927                                                  resources[i].param) < 0) {
928                     log_verbose("Cannot set resource %s", resources[i].name);
929                     return -1;
930                 }
931                 break;
932             case RES_STRING:
933                 if ((*resources[i].set_func_string)((const char *)(resources[i].factory_value),
934                                                     resources[i].param) < 0) {
935                     log_verbose("Cannot set resource %s", resources[i].name);
936                     return -1;
937                 }
938                 break;
939         }
940 
941         resources_issue_callback(resources + i, 0);
942     }
943 
944     if (resource_modified_callback != NULL) {
945         resources_exec_callback_chain(resource_modified_callback, NULL);
946     }
947 
948     return 0;
949 }
950 
resources_set_event_safe(void)951 int resources_set_event_safe(void)
952 {
953     unsigned int i;
954 
955     for (i = 0; i < num_resources; i++) {
956         switch (resources[i].type) {
957             case RES_INTEGER:
958                 if (resources[i].event_relevant == RES_EVENT_STRICT) {
959                     if ((*resources[i].set_func_int)(vice_ptr_to_int(resources[i].event_strict_value),
960                                                      resources[i].param) < 0) {
961                         log_error(LOG_DEFAULT, "failed to set event-safe resource value for '%s'\n", resources[i].name);
962                         return -1;
963                     }
964                 }
965                 break;
966             case RES_STRING:
967                 if (resources[i].event_relevant == RES_EVENT_STRICT) {
968                     if ((*resources[i].set_func_string)((const char *)(resources[i].event_strict_value),
969                                                         resources[i].param) < 0) {
970                         log_error(LOG_DEFAULT, "failed to set event-safe resource value for '%s'\n", resources[i].name);
971                         return -1;
972                     }
973                 }
974                 break;
975         }
976         resources_issue_callback(resources + i, 0);
977     }
978 
979     if (resource_modified_callback != NULL) {
980         resources_exec_callback_chain(resource_modified_callback, NULL);
981     }
982 
983     return 0;
984 }
985 
resources_get_event_safe_list(event_list_state_t * list)986 void resources_get_event_safe_list(event_list_state_t *list)
987 {
988     unsigned int i;
989     char *event_data;
990     int data_size;
991 
992     for (i = 0; i < num_resources; i++) {
993         if (resources[i].event_relevant == RES_EVENT_SAME) {
994             resource_create_event_data(&event_data, &data_size,
995                                        &resources[i],
996                                        *(resources[i].value_ptr));
997             event_record_in_list(list, EVENT_RESOURCE,
998                                  event_data, data_size);
999             lib_free(event_data);
1000         }
1001     }
1002     event_record_in_list(list, EVENT_LIST_END, NULL, 0);
1003 }
1004 
resources_toggle(const char * name,int * new_value_return)1005 int resources_toggle(const char *name, int *new_value_return)
1006 {
1007     resource_ram_t *r = lookup(name);
1008     int value;
1009 
1010     if (r == NULL) {
1011         log_warning(LOG_DEFAULT,
1012                     "Trying to toggle boolean value of unknown "
1013                     "resource `%s'.", name);
1014         return -1;
1015     }
1016 
1017     value = !(*(int *)r->value_ptr);
1018 
1019     if (r->event_relevant == RES_EVENT_STRICT && network_get_mode() != NETWORK_IDLE) {
1020         return -2;
1021     }
1022 
1023     if (new_value_return != NULL) {
1024         *new_value_return = value;
1025     }
1026 
1027     if (r->event_relevant == RES_EVENT_SAME && network_connected()) {
1028         resource_record_event(r, uint_to_void_ptr(value));
1029         return 0;
1030     }
1031 
1032     return resources_set_internal_int(r, value);
1033 }
1034 
resources_touch(const char * name)1035 int resources_touch(const char *name)
1036 {
1037     void *tmp;
1038 
1039     if (resources_get_value(name, (resource_value_t *)&tmp) < 0) {
1040         return -1;
1041     }
1042 
1043     return resources_set_value(name, (resource_value_t)tmp);
1044 }
1045 
1046 /* ------------------------------------------------------------------------- */
1047 
1048 /* Check whether `buf' is the emulator ID for the machine we are emulating.  */
check_emu_id(const char * buf)1049 static int check_emu_id(const char *buf)
1050 {
1051     size_t machine_id_len, buf_len;
1052 
1053     buf_len = strlen(buf);
1054     if (*buf != '[' || *(buf + buf_len - 1) != ']') {
1055         return 0;
1056     }
1057 
1058     if (machine_id == NULL) {
1059         return 1;
1060     }
1061 
1062     machine_id_len = strlen(machine_id);
1063     if (machine_id_len != buf_len - 2) {
1064         return 0;
1065     }
1066 
1067     if (strncmp(buf + 1, machine_id, machine_id_len) == 0) {
1068         return 1;
1069     } else {
1070         return 0;
1071     }
1072 }
1073 
1074 /* ------------------------------------------------------------------------- */
1075 
1076 /* Read one resource line from the file descriptor `f'.
1077    Returns:
1078     1 on success,
1079     0 on EOF or end of emulator section.
1080    -1 on general error
1081    RESERR_TYPE_INVALID on parse/type error
1082    RESERR_UNKNOWN_RESOURCE on unknown resource error
1083 */
1084 /* FIXME: make event safe */
resources_read_item_from_file(FILE * f)1085 int resources_read_item_from_file(FILE *f)
1086 {
1087     char buf[1024];
1088     char *arg_ptr;
1089     int line_len, resname_len;
1090     size_t arg_len;
1091     resource_ram_t *r;
1092 
1093     line_len = util_get_line(buf, 1024, f);
1094 
1095     if (line_len < 0) {
1096         return 0;
1097     }
1098 
1099     /* Ignore empty lines.  */
1100     if (*buf == '\0') {
1101         return 1;
1102     }
1103 
1104     if (*buf == '[') {
1105         /* End of emulator-specific section.  */
1106         return 0;
1107     }
1108 
1109     arg_ptr = strchr(buf, '=');
1110     if (!arg_ptr) {
1111         return -1;
1112     }
1113 
1114     resname_len = (int)(arg_ptr - buf);
1115     arg_ptr++;
1116     arg_len = strlen(arg_ptr);
1117 
1118     /* If the value is between quotes, remove them.  */
1119     if (*arg_ptr == '"' && *(arg_ptr + arg_len - 1) == '"') {
1120         *(arg_ptr + arg_len - 1) = '\0';
1121         arg_ptr++;
1122     }
1123 
1124     *(buf + resname_len) = '\0';
1125 
1126     {
1127         int result;
1128 
1129         r = lookup(buf);
1130         if (r == NULL) {
1131             log_error(LOG_DEFAULT, "Unknown resource `%s'.", buf);
1132             return RESERR_UNKNOWN_RESOURCE;
1133         }
1134 
1135         switch (r->type) {
1136             case RES_INTEGER:
1137                 result = (*r->set_func_int)(atoi(arg_ptr), r->param);
1138                 break;
1139             case RES_STRING:
1140                 result = (*r->set_func_string)(arg_ptr, r->param);
1141                 break;
1142             default:
1143                 log_error(LOG_DEFAULT, "Unknown resource type for `%s'.",
1144                           r->name);
1145                 result = RESERR_TYPE_INVALID;
1146                 break;
1147         }
1148 
1149         if (result < 0) {
1150             switch (r->type) {
1151                 case RES_INTEGER:
1152                 case RES_STRING:
1153                     log_error(LOG_DEFAULT, "Cannot assign value `%s' to resource `%s'.", arg_ptr, r->name);
1154                     break;
1155                 default:
1156                     log_error(LOG_DEFAULT, "Cannot assign value to resource `%s'.", r->name);
1157                     break;
1158             }
1159             return -1;
1160         }
1161 
1162         resources_issue_callback(r, 0);
1163 
1164         return 1;
1165     }
1166 }
1167 
1168 /* Load the resources from file `fname'.  If `fname' is NULL, load them from
1169    the default resource file.  */
resources_load(const char * fname)1170 int resources_load(const char *fname)
1171 {
1172     FILE *f;
1173     int retval;
1174     int line_num;
1175     int err = 0;
1176 
1177     f = fopen(fname, MODE_READ_TEXT);
1178     if (f == NULL) {
1179         return RESERR_FILE_NOT_FOUND;
1180     }
1181 
1182     log_message(LOG_DEFAULT, "Reading configuration file `%s'.", fname);
1183 
1184     /* Find the start of the configuration section for this emulator.  */
1185     for (line_num = 1;; line_num++) {
1186         char buf[1024];
1187 
1188         if (util_get_line(buf, 1024, f) < 0) {
1189             fclose(f);
1190             return RESERR_READ_ERROR;
1191         }
1192 
1193         if (check_emu_id(buf)) {
1194             line_num++;
1195             break;
1196         }
1197     }
1198 
1199     do {
1200         retval = resources_read_item_from_file(f);
1201         switch (retval) {
1202             case RESERR_TYPE_INVALID:
1203                     log_error(LOG_DEFAULT,
1204                             "%s: Invalid resource specification at line %d.",
1205                             fname, line_num);
1206                     err = 1;
1207                 break;
1208             case RESERR_UNKNOWN_RESOURCE:
1209                     log_warning(LOG_DEFAULT,
1210                                 "%s: Unknown resource specification at line %d.",
1211                                 fname, line_num);
1212                 break;
1213         }
1214         line_num++;
1215     } while (retval != 0);
1216 
1217     fclose(f);
1218 
1219     if (resource_modified_callback != NULL) {
1220         resources_exec_callback_chain(resource_modified_callback, NULL);
1221     }
1222 
1223     return err ? RESERR_FILE_INVALID : 0;
1224 }
1225 
1226 /* Reset resources to defaults, then load the resources from file `fname'.
1227    If `fname' is NULL, load them from the default resource file.  */
resources_reset_and_load(const char * fname)1228 int resources_reset_and_load(const char *fname)
1229 {
1230     char *default_name = NULL;
1231     int res;
1232     if (fname == NULL) {
1233         if (vice_config_file == NULL) {
1234             /* try the alternative name/location first */
1235             default_name = archdep_default_portable_resource_file_name();
1236             if (default_name != NULL) {
1237                 if (ioutil_access(default_name, IOUTIL_ACCESS_R_OK) != 0)  {
1238                     /* if not found at alternative location, try the normal one */
1239                     lib_free(default_name);
1240                     default_name = archdep_default_resource_file_name();
1241                 }
1242             }
1243         } else {
1244             default_name = lib_strdup(vice_config_file);
1245         }
1246         fname = default_name;
1247     }
1248     resources_set_defaults();
1249     res = resources_load(fname);
1250     lib_free(default_name);
1251     return res;
1252 }
1253 
string_resource_item(int num,const char * delim)1254 static char *string_resource_item(int num, const char *delim)
1255 {
1256     char *line = NULL;
1257     resource_value_t v;
1258 
1259     switch (resources[num].type) {
1260         case RES_INTEGER:
1261             v = (resource_value_t) uint_to_void_ptr(*(int *)resources[num].value_ptr);
1262             line = lib_msprintf("%s=%d%s", resources[num].name, vice_ptr_to_int(v), delim);
1263             break;
1264         case RES_STRING:
1265             v = *resources[num].value_ptr;
1266             if ((char *)v != NULL) {
1267                 line = lib_msprintf("%s=\"%s\"%s", resources[num].name, (char *)v,
1268                                     delim);
1269             } else {
1270                 line = lib_msprintf("%s=%s", resources[num].name, delim);
1271             }
1272             break;
1273         default:
1274             log_error(LOG_DEFAULT, "Unknown value type for resource `%s'.",
1275                       resources[num].name);
1276             break;
1277     }
1278     return line;
1279 }
1280 
1281 /* Write the resource specification for resource number `num' to file
1282    descriptor `f'.  */
write_resource_item(FILE * f,int num)1283 static void write_resource_item(FILE *f, int num)
1284 {
1285     char *line;
1286 
1287     line = string_resource_item(num, "\n");
1288 
1289     if (line != NULL) {
1290         fputs(line, f);
1291         lib_free(line);
1292     }
1293 }
1294 
1295 /* check if a resource contains its default value */
resource_item_isdefault(int num)1296 static int resource_item_isdefault(int num)
1297 {
1298     int i1, i2;
1299     char *s1, *s2;
1300     resource_value_t v;
1301 
1302     switch (resources[num].type) {
1303         case RES_INTEGER:
1304             v = (resource_value_t) uint_to_void_ptr(*(int *)resources[num].value_ptr);
1305             i1 = vice_ptr_to_int(v);
1306             i2 = vice_ptr_to_int(resources[num].factory_value);
1307             if (i1 == i2) {
1308                 return 1;
1309             }
1310             DBG(("%s = (int) default: \"%d\" is: \"%d\"\n", resources[num].name, i2, i1));
1311             break;
1312         case RES_STRING:
1313             v = *resources[num].value_ptr;
1314             s1 = (char *)v == NULL ? "" : (char *)v;
1315             s2 = (char *)resources[num].factory_value == NULL ? "" : (char *)resources[num].factory_value;
1316             if (!strcmp(s1, s2)) {
1317                 return 1;
1318             }
1319             DBG(("%s = (string) default: \"%s\" is: \"%s\"\n", resources[num].name, s2, s1));
1320             break;
1321         default:
1322             log_error(LOG_DEFAULT, "Unknown value type for resource `%s'.", resources[num].name);
1323             break;
1324     }
1325     return 0;
1326 }
1327 
1328 /* Save all the resources into file `fname'.  If `fname' is NULL, save them
1329    in the default resource file.  Writing the resources does not destroy the
1330    resources for the other emulators.  */
resources_save(const char * fname)1331 int resources_save(const char *fname)
1332 {
1333     char *backup_name = NULL;
1334     FILE *in_file = NULL, *out_file;
1335     unsigned int i;
1336     char *default_name = NULL;
1337 
1338     /* get name for config file */
1339     if (fname == NULL) {
1340         if (vice_config_file == NULL) {
1341             /* try the alternative name/location first */
1342             default_name = archdep_default_portable_resource_file_name();
1343             if (default_name != NULL) {
1344                 if (ioutil_access(default_name, IOUTIL_ACCESS_R_OK) != 0) {
1345                     /* if not found at alternative location, try the normal one
1346                      this also creates the .vice directory if not present */
1347                     lib_free(default_name);
1348                     default_name = archdep_default_resource_file_name();
1349                 }
1350             }
1351         } else {
1352             default_name = lib_strdup(vice_config_file);
1353         }
1354         fname = default_name;
1355     }
1356 
1357     /* make a backup of an existing config, open it */
1358     if (util_file_exists(fname) != 0) {
1359         /* try to open it */
1360         if (ioutil_access(fname, IOUTIL_ACCESS_W_OK) != 0) {
1361             lib_free(default_name);
1362             return RESERR_WRITE_PROTECTED;
1363         }
1364         /* get backup name */
1365         backup_name = archdep_make_backup_filename(fname);
1366         /* if backup exists, remove it */
1367         if (util_file_exists(backup_name) != 0) {
1368             if (ioutil_access(backup_name, IOUTIL_ACCESS_W_OK) != 0) {
1369                 lib_free(backup_name);
1370                 lib_free(default_name);
1371                 return RESERR_WRITE_PROTECTED;
1372             }
1373             if (ioutil_remove(backup_name) != 0) {
1374                 lib_free(backup_name);
1375                 lib_free(default_name);
1376                 return RESERR_CANNOT_REMOVE_BACKUP;
1377             }
1378         }
1379         /* move existing config to backup */
1380         if (ioutil_rename(fname, backup_name) != 0) {
1381             lib_free(backup_name);
1382             lib_free(default_name);
1383             return RESERR_CANNOT_RENAME_FILE;
1384         }
1385         /* open the old config */
1386         in_file = fopen(backup_name, MODE_READ_TEXT);
1387         if (!in_file) {
1388             lib_free(backup_name);
1389             lib_free(default_name);
1390             return RESERR_READ_ERROR;
1391         }
1392     }
1393 
1394     log_message(LOG_DEFAULT, "Writing configuration file `%s'.", fname);
1395 
1396     out_file = fopen(fname, MODE_WRITE_TEXT);
1397 
1398     if (!out_file) {
1399         if (in_file != NULL) {
1400             fclose(in_file);
1401         }
1402         lib_free(backup_name);
1403         lib_free(default_name);
1404         return RESERR_CANNOT_CREATE_FILE;
1405     }
1406 
1407     setbuf(out_file, NULL);
1408 
1409     /* Copy the configuration for the other emulators.  */
1410     if (in_file != NULL) {
1411         while (1) {
1412             char buf[1024];
1413 
1414             if (util_get_line(buf, 1024, in_file) < 0) {
1415                 break;
1416             }
1417 
1418             if (check_emu_id(buf)) {
1419                 break;
1420             }
1421 
1422             fprintf(out_file, "%s\n", buf);
1423         }
1424     }
1425 
1426     /* Write our current configuration.  */
1427     fprintf(out_file, "[%s]\n", machine_id);
1428     for (i = 0; i < num_resources; i++) {
1429         /* only dump into the file what is different to the default config */
1430         if (!resource_item_isdefault(i)) {
1431             write_resource_item(out_file, i);
1432         }
1433     }
1434     fprintf(out_file, "\n");
1435 
1436     if (in_file != NULL) {
1437         char buf[1024];
1438 
1439         /* Skip the old configuration for this emulator.  */
1440         while (1) {
1441             if (util_get_line(buf, 1024, in_file) < 0) {
1442                 break;
1443             }
1444 
1445             /* Check if another emulation section starts.  */
1446             if (*buf == '[') {
1447                 fprintf(out_file, "%s\n", buf);
1448                 break;
1449             }
1450         }
1451 
1452         if (!feof(in_file)) {
1453             /* Copy the configuration for the other emulators.  */
1454             while (util_get_line(buf, 1024, in_file) >= 0) {
1455                 fprintf(out_file, "%s\n", buf);
1456             }
1457         }
1458         fclose(in_file);
1459         /* remove the backup */
1460         ioutil_remove(backup_name);
1461     }
1462 
1463     fclose(out_file);
1464     lib_free(backup_name);
1465     lib_free(default_name);
1466     return 0;
1467 }
1468 
1469 /* dump ALL resources of the current machine into a file */
resources_dump(const char * fname)1470 int resources_dump(const char *fname)
1471 {
1472     FILE *out_file;
1473     unsigned int i;
1474 
1475     log_message(LOG_DEFAULT, "Dumping %u resources to file `%s'.",
1476             num_resources, fname);
1477 
1478     out_file = fopen(fname, MODE_WRITE_TEXT);
1479     if (!out_file) {
1480         return RESERR_CANNOT_CREATE_FILE;
1481     }
1482 
1483     setbuf(out_file, NULL);
1484 
1485     /* Write our current configuration.  */
1486     fprintf(out_file, "[%s]\n", machine_id);
1487     for (i = 0; i < num_resources; i++) {
1488         write_resource_item(out_file, i);
1489     }
1490     fprintf(out_file, "\n");
1491 
1492     fclose(out_file);
1493     return 0;
1494 }
1495 
resources_register_callback(const char * name,resource_callback_func_t * callback,void * callback_param)1496 int resources_register_callback(const char *name,
1497                                 resource_callback_func_t *callback,
1498                                 void *callback_param)
1499 {
1500     if (name == NULL) {
1501         resources_add_callback(&resource_modified_callback, callback,
1502                                callback_param);
1503         return 0;
1504     } else {
1505         resource_ram_t *res = lookup(name);
1506 
1507         if (res != NULL) {
1508             resources_add_callback(&(res->callback), callback, callback_param);
1509             return 0;
1510         }
1511     }
1512     return -1;
1513 }
1514