kolibrios-fun/programs/emulator/dgen-sdl-1.33/rc.cpp

1236 lines
35 KiB
C++
Raw Normal View History

// This parses the RC file.
#ifdef __MINGW32__
#undef __STRICT_ANSI__
#endif
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <assert.h>
#include <strings.h>
#include <ctype.h>
#include "rc.h"
#include "ckvp.h"
#include "pd-defs.h"
#include "romload.h"
#include "system.h"
#include "md.h"
// CTV names
const char *ctv_names[] = {
"off", "blur", "scanline", "interlace", "swab",
NULL
};
// Scaling algorithms names
const char *scaling_names[] = {
"stretch",
"scale",
"hqx",
"hqx stretch",
"scale2x",
"scale2x stretch",
"none",
NULL
};
// CPU names, keep index in sync with rc-vars.h and enums in md.h
const char *emu_z80_names[] = { "none", "mz80", "cz80", "drz80", NULL };
const char *emu_m68k_names[] = { "none", "star", "musa", "cyclone", NULL };
// The table of strings and the keysyms they map to.
// The order is a bit weird, since this was originally a mapping for the SVGALib
// scancodes, and I just added the SDL stuff on top of it.
struct rc_keysym rc_keysyms[] = {
{ "ESCAPE", PDK_ESCAPE },
{ "BACKSPACE", PDK_BACKSPACE },
{ "TAB", PDK_TAB },
{ "RETURN", PDK_RETURN },
{ "ENTER", PDK_RETURN },
{ "KP_MULTIPLY", PDK_KP_MULTIPLY },
{ "SPACE", PDK_SPACE },
{ "F1", PDK_F1 },
{ "F2", PDK_F2 },
{ "F3", PDK_F3 },
{ "F4", PDK_F4 },
{ "F5", PDK_F5 },
{ "F6", PDK_F6 },
{ "F7", PDK_F7 },
{ "F8", PDK_F8 },
{ "F9", PDK_F9 },
{ "F10", PDK_F10 },
{ "KP_7", PDK_KP7 },
{ "KP_HOME", PDK_KP7 },
{ "KP_8", PDK_KP8 },
{ "KP_UP", PDK_KP8 },
{ "KP_9", PDK_KP9 },
{ "KP_PAGE_UP", PDK_KP9 },
{ "KP_PAGEUP", PDK_KP9 },
{ "KP_MINUS", PDK_KP_MINUS },
{ "KP_4", PDK_KP4 },
{ "KP_LEFT", PDK_KP4 },
{ "KP_5", PDK_KP5 },
{ "KP_6", PDK_KP6 },
{ "KP_RIGHT", PDK_KP6 },
{ "KP_PLUS", PDK_KP_PLUS },
{ "KP_1", PDK_KP1 },
{ "KP_END", PDK_KP1 },
{ "KP_2", PDK_KP2 },
{ "KP_DOWN", PDK_KP2 },
{ "KP_3", PDK_KP3 },
{ "KP_PAGE_DOWN", PDK_KP3 },
{ "KP_PAGEDOWN", PDK_KP3 },
{ "KP_0", PDK_KP0 },
{ "KP_INSERT", PDK_KP0 },
{ "KP_PERIOD", PDK_KP_PERIOD },
{ "KP_DELETE", PDK_KP_PERIOD },
{ "F11", PDK_F11 },
{ "F12", PDK_F12 },
{ "KP_ENTER", PDK_KP_ENTER },
{ "KP_DIVIDE", PDK_KP_DIVIDE },
{ "HOME", PDK_HOME },
{ "UP", PDK_UP },
{ "PAGE_UP", PDK_PAGEUP },
{ "PAGEUP", PDK_PAGEUP },
{ "LEFT", PDK_LEFT },
{ "RIGHT", PDK_RIGHT },
{ "END", PDK_END },
{ "DOWN", PDK_DOWN },
{ "PAGE_DOWN", PDK_PAGEDOWN },
{ "PAGEDOWN", PDK_PAGEDOWN },
{ "INSERT", PDK_INSERT },
{ "DELETE", PDK_DELETE },
{ "NUMLOCK", PDK_NUMLOCK },
{ "NUM_LOCK", PDK_NUMLOCK },
{ "CAPSLOCK", PDK_CAPSLOCK },
{ "CAPS_LOCK", PDK_CAPSLOCK },
{ "SCROLLOCK", PDK_SCROLLOCK },
{ "SCROLL_LOCK", PDK_SCROLLOCK },
{ "LSHIFT", PDK_LSHIFT },
{ "SHIFT_L", PDK_LSHIFT },
{ "RSHIFT", PDK_RSHIFT },
{ "SHIFT_R", PDK_RSHIFT },
{ "LCTRL", PDK_LCTRL },
{ "CTRL_L", PDK_LCTRL },
{ "RCTRL", PDK_RCTRL },
{ "CTRL_R", PDK_RCTRL },
{ "LALT", PDK_LALT },
{ "ALT_L", PDK_LALT },
{ "RALT", PDK_RALT },
{ "ALT_R", PDK_RALT },
{ "LMETA", PDK_LMETA },
{ "META_L", PDK_LMETA },
{ "RMETA", PDK_RMETA },
{ "META_R", PDK_RMETA },
{ "", 0 },
{ NULL, 0 } // Terminator
}; // Phew! ;)
/* Define all the external RC variables */
#include "rc-vars.h"
static const struct {
const char *name;
uint32_t flag;
} keymod[] = {
{ "shift-", KEYSYM_MOD_SHIFT },
{ "ctrl-", KEYSYM_MOD_CTRL },
{ "alt-", KEYSYM_MOD_ALT },
{ "meta-", KEYSYM_MOD_META },
{ "", 0 }
};
/* Parse a keysym.
* If the string matches one of the strings in the keysym table above
* or if it's another valid character, return the keysym, otherwise -1. */
intptr_t rc_keysym(const char *code, intptr_t *)
{
struct rc_keysym *ks = rc_keysyms;
uint32_t m = 0;
uint32_t c;
while (*code != '\0') {
size_t i;
for (i = 0; (keymod[i].name[0] != '\0'); ++i) {
size_t l = strlen(keymod[i].name);
if (strncasecmp(keymod[i].name, code, l))
continue;
m |= keymod[i].flag;
code += l;
break;
}
if (keymod[i].name[0] == '\0')
break;
}
while (ks->name != NULL) {
if (!strcasecmp(ks->name, code))
return (m | ks->keysym);
++ks;
}
/* Not in the list, so expect a single UTF-8 character instead. */
code += utf8u32(&c, (const uint8_t *)code);
if ((c == (uint32_t)-1) || (*code != '\0'))
return -1;
/* Must fit in 16 bits. */
if (c > 0xffff)
return -1;
return (m | (c & 0xffff));
}
/* Convert a keysym to text. */
char *dump_keysym(intptr_t k)
{
char buf[64];
size_t i;
size_t n;
size_t l = 0;
struct rc_keysym *ks = rc_keysyms;
buf[0] = '\0';
for (i = 0; ((keymod[i].name[0] != '\0') && (l < sizeof(buf))); ++i)
if (k & keymod[i].flag) {
n = strlen(keymod[i].name);
if (n > (sizeof(buf) - l))
n = (sizeof(buf) - l);
memcpy(&buf[l], keymod[i].name, n);
l += n;
}
k &= ~KEYSYM_MOD_MASK;
while (ks->name != NULL) {
if (ks->keysym == k) {
n = strlen(ks->name);
if (n > (sizeof(buf) - l))
n = (sizeof(buf) - l);
memcpy(&buf[l], ks->name, n);
l += n;
goto found;
}
++ks;
}
n = utf32u8(NULL, k);
if ((n == 0) || (n > (sizeof(buf) - l)))
return NULL;
utf32u8((uint8_t *)&buf[l], k);
l += n;
found:
return backslashify((uint8_t *)buf, l, 0, NULL);
}
/* Parse a boolean value.
* If the string is "yes" or "true", return 1.
* If the string is "no" or "false", return 0.
* Otherwise, just return atoi(value). */
intptr_t rc_boolean(const char *value, intptr_t *)
{
if(!strcasecmp(value, "yes") || !strcasecmp(value, "true"))
return 1;
if(!strcasecmp(value, "no") || !strcasecmp(value, "false"))
return 0;
return atoi(value);
}
static const char *joypad_axis_type[] = {
"max", "p", "positive",
"min", "n", "negative",
"between", "b", NULL
};
static const unsigned int joypad_axis_value[] = {
JS_AXIS_POSITIVE, JS_AXIS_POSITIVE, JS_AXIS_POSITIVE,
JS_AXIS_NEGATIVE, JS_AXIS_NEGATIVE, JS_AXIS_NEGATIVE,
JS_AXIS_BETWEEN, JS_AXIS_BETWEEN
};
static const char *joypad_hat_type[] = {
"centered", "up", "right", "down", "left"
};
static const unsigned int joypad_hat_value[] = {
JS_HAT_CENTERED, JS_HAT_UP, JS_HAT_RIGHT, JS_HAT_DOWN, JS_HAT_LEFT
};
/* Convert a joypad entry to text. */
char *dump_joypad(intptr_t js)
{
char *str = NULL;
unsigned int id = JS_GET_IDENTIFIER(js);
const char *arg0 = NULL;
unsigned int val0 = 0;
const char *arg1 = NULL;
unsigned int i;
if (JS_IS_BUTTON(js)) {
arg0 = "button";
val0 = JS_GET_BUTTON(js);
}
else if (JS_IS_AXIS(js)) {
arg0 = "axis";
val0 = JS_GET_AXIS(js);
for (i = 0; (i != elemof(joypad_axis_value)); ++i)
if (joypad_axis_value[i] == JS_GET_AXIS_DIR(js)) {
arg1 = joypad_axis_type[i];
break;
}
if (arg1 == NULL)
return NULL;
}
else if (JS_IS_HAT(js)) {
arg0 = "hat";
val0 = JS_GET_HAT(js);
for (i = 0; (i != elemof(joypad_hat_value)); ++i)
if (joypad_hat_value[i] == JS_GET_HAT_DIR(js)) {
arg1 = joypad_hat_type[i];
break;
}
if (arg1 == NULL)
return NULL;
}
else
return NULL;
i = 0;
while (1) {
i = snprintf(str, i, "joystick%u-%s%u%s%s",
id, arg0, val0,
(arg1 ? "-" : ""), (arg1 ? arg1 : ""));
if ((str != NULL) ||
((str = (char *)malloc(++i)) == NULL))
break;
}
return str;
}
/*
* Parse a joystick/joypad command (joy_*). The following syntaxes are
* supported:
*
* Normal buttons:
* (j|js|joystick|joypad)X-(b|button)Y
* Axes:
* (j|js|joystick|joypad)X-(a|axis)Y-(max|p|positive|min|n|negative)
* Hats:
* (j|js|joystick|joypad)X-(h|hat)Y-(up|right|down|left)
*/
intptr_t rc_joypad(const char *value, intptr_t *)
{
static const char *r_js[] = { "j", "js", "joystick", "joypad", NULL };
static const char *r_button[] = { "b", "button", NULL };
static const char *r_axis[] = { "a", "axis", NULL };
static const char **r_axis_type = joypad_axis_type;
static const unsigned int *r_axis_value = joypad_axis_value;
static const char *r_hat[] = { "h", "hat", NULL };
static const char **r_hat_type = joypad_hat_type;
static const unsigned int *r_hat_value = joypad_hat_value;
int i;
unsigned int id;
unsigned int arg;
if (*value == '\0')
return 0;
if ((i = prefix_casematch(value, r_js)) == -1)
return -1;
value += strlen(r_js[i]);
if ((i = prefix_getuint(value, &id)) == 0)
return -1;
value += i;
if (*value != '-')
return -1;
++value;
if ((i = prefix_casematch(value, r_button)) != -1) {
value += strlen(r_button[i]);
if ((i = prefix_getuint(value, &arg)) == 0)
return -1;
value += i;
if (*value != '\0')
return -1;
return JS_BUTTON(id, arg);
}
if ((i = prefix_casematch(value, r_axis)) != -1) {
value += strlen(r_axis[i]);
if ((i = prefix_getuint(value, &arg)) == 0)
return -1;
value += i;
if (*value != '-')
return -1;
++value;
if ((i = prefix_casematch(value, r_axis_type)) == -1)
return -1;
value += strlen(r_axis_type[i]);
if (*value != '\0')
return -1;
return JS_AXIS(id, arg, r_axis_value[i]);
}
if ((i = prefix_casematch(value, r_hat)) != -1) {
value += strlen(r_hat[i]);
if ((i = prefix_getuint(value, &arg)) == 0)
return -1;
value += i;
if (*value != '-')
return -1;
++value;
if ((i = prefix_casematch(value, r_hat_type)) == -1)
return -1;
value += strlen(r_hat_type[i]);
if (*value != '\0')
return -1;
return JS_HAT(id, arg, r_hat_value[i]);
}
return -1;
}
static const char *mouse_motion_type[] = {
"up", "u",
"down", "d",
"left", "l",
"right", "r",
NULL
};
/* Convert a mouse entry to text. */
char *dump_mouse(intptr_t mo)
{
char *str = NULL;
unsigned int id = MO_GET_IDENTIFIER(mo);
const char *arg0 = NULL;
unsigned int val0 = 0;
unsigned int i;
if (MO_IS_BUTTON(mo)) {
arg0 = "button";
val0 = MO_GET_BUTTON(mo);
}
else if (MO_IS_MOTION(mo)) {
val0 = MO_GET_MOTION(mo);
for (i = 0; (i != elemof(mouse_motion_type)); ++i)
if (mouse_motion_type[i][0] == (char)val0) {
arg0 = mouse_motion_type[i];
break;
}
if (arg0 == NULL)
return NULL;
}
else
return NULL;
i = 0;
while (1) {
if (MO_IS_BUTTON(mo))
i = snprintf(str, i, "mouse%u-%s%u", id, arg0, val0);
else
i = snprintf(str, i, "mouse%u-%s", id, arg0);
if ((str != NULL) ||
((str = (char *)malloc(++i)) == NULL))
break;
}
return str;
}
/*
* Parse a mouse command (mou_*). The following syntaxes are
* supported:
*
* Buttons:
* (m|mo|mouse)X-(b|button)Y
* Motions:
* (m|mo|mouse)X-(u|up|d|down|l|left|r|right)
*/
intptr_t rc_mouse(const char *value, intptr_t *)
{
static const char *r_mo[] = { "m", "mo", "mouse", NULL };
static const char *r_button[] = { "b", "button", NULL };
static const char **r_motion_type = mouse_motion_type;
int i;
unsigned int id;
unsigned int arg;
if (*value == '\0')
return 0;
if ((i = prefix_casematch(value, r_mo)) == -1)
return -1;
value += strlen(r_mo[i]);
if ((i = prefix_getuint(value, &id)) == 0)
return -1;
value += i;
if (*value != '-')
return -1;
++value;
if ((i = prefix_casematch(value, r_button)) != -1) {
value += strlen(r_button[i]);
if ((i = prefix_getuint(value, &arg)) == 0)
return -1;
value += i;
if (*value != '\0')
return -1;
return MO_BUTTON(id, arg);
}
if ((i = prefix_casematch(value, r_motion_type)) != -1) {
arg = r_motion_type[i][0];
value += strlen(r_motion_type[i]);
if (*value != '\0')
return -1;
return MO_MOTION(id, arg);
}
return -1;
}
/* Parse the CTV type. As new CTV filters get submitted expect this to grow ;)
* Current values are:
* off - No CTV
* blur - blur bitmap (from DirectX DGen), by Dave <dave@dtmnt.com>
* scanline - attenuates every other line, looks cool! by Phillip K. Hornung <redx@pknet.com>
*/
intptr_t rc_ctv(const char *value, intptr_t *)
{
for(int i = 0; i < NUM_CTV; ++i)
if(!strcasecmp(value, ctv_names[i])) return i;
return -1;
}
intptr_t rc_scaling(const char *value, intptr_t *)
{
int i;
for (i = 0; (i < NUM_SCALING); ++i)
if (!strcasecmp(value, scaling_names[i]))
return i;
return -1;
}
/* Parse CPU types */
intptr_t rc_emu_z80(const char *value, intptr_t *)
{
unsigned int i;
for (i = 0; (emu_z80_names[i] != NULL); ++i)
if (!strcasecmp(value, emu_z80_names[i]))
return i;
return -1;
}
intptr_t rc_emu_m68k(const char *value, intptr_t *)
{
unsigned int i;
for (i = 0; (emu_m68k_names[i] != NULL); ++i)
if (!strcasecmp(value, emu_m68k_names[i]))
return i;
return -1;
}
intptr_t rc_region(const char *value, intptr_t *)
{
if (strlen(value) != 1)
return -1;
switch (value[0] | 0x20) {
case 'j':
case 'x':
case 'u':
case 'e':
case ' ':
return (value[0] & ~0x20);
}
return -1;
}
intptr_t rc_string(const char *value, intptr_t *)
{
char *val;
if ((val = strdup(value)) == NULL)
return -1;
// -1 is reserved, thus invalid. Should not happen anyway.
if ((intptr_t)val == -1)
abort();
return (intptr_t)val;
}
intptr_t rc_rom_path(const char *value, intptr_t *)
{
intptr_t r = rc_string(value, NULL);
if (r == -1)
return -1;
set_rom_path((char *)r);
return r;
}
intptr_t rc_number(const char *value, intptr_t *)
{
return strtol(value, NULL, 0);
}
intptr_t rc_soundrate(const char *value, intptr_t *)
{
long r = strtol(value, NULL, 0);
if (r < 8000)
r = 8000;
return r;
}
/* This is a table of all the RC options, the variables they affect, and the
* functions to parse their values. */
struct rc_field rc_fields[RC_FIELDS_SIZE] = {
{ "key_pad1_up", rc_keysym, &pad1_up[RCBK] },
{ "joy_pad1_up", rc_joypad, &pad1_up[RCBJ] },
{ "mou_pad1_up", rc_mouse, &pad1_up[RCBM] },
{ "key_pad1_down", rc_keysym, &pad1_down[RCBK] },
{ "joy_pad1_down", rc_joypad, &pad1_down[RCBJ] },
{ "mou_pad1_down", rc_mouse, &pad1_down[RCBM] },
{ "key_pad1_left", rc_keysym, &pad1_left[RCBK] },
{ "joy_pad1_left", rc_joypad, &pad1_left[RCBJ] },
{ "mou_pad1_left", rc_mouse, &pad1_left[RCBM] },
{ "key_pad1_right", rc_keysym, &pad1_right[RCBK] },
{ "joy_pad1_right", rc_joypad, &pad1_right[RCBJ] },
{ "mou_pad1_right", rc_mouse, &pad1_right[RCBM] },
{ "key_pad1_a", rc_keysym, &pad1_a[RCBK] },
{ "joy_pad1_a", rc_joypad, &pad1_a[RCBJ] },
{ "mou_pad1_a", rc_mouse, &pad1_a[RCBM] },
{ "key_pad1_b", rc_keysym, &pad1_b[RCBK] },
{ "joy_pad1_b", rc_joypad, &pad1_b[RCBJ] },
{ "mou_pad1_b", rc_mouse, &pad1_b[RCBM] },
{ "key_pad1_c", rc_keysym, &pad1_c[RCBK] },
{ "joy_pad1_c", rc_joypad, &pad1_c[RCBJ] },
{ "mou_pad1_c", rc_mouse, &pad1_c[RCBM] },
{ "key_pad1_x", rc_keysym, &pad1_x[RCBK] },
{ "joy_pad1_x", rc_joypad, &pad1_x[RCBJ] },
{ "mou_pad1_x", rc_mouse, &pad1_x[RCBM] },
{ "key_pad1_y", rc_keysym, &pad1_y[RCBK] },
{ "joy_pad1_y", rc_joypad, &pad1_y[RCBJ] },
{ "mou_pad1_y", rc_mouse, &pad1_y[RCBM] },
{ "key_pad1_z", rc_keysym, &pad1_z[RCBK] },
{ "joy_pad1_z", rc_joypad, &pad1_z[RCBJ] },
{ "mou_pad1_z", rc_mouse, &pad1_z[RCBM] },
{ "key_pad1_mode", rc_keysym, &pad1_mode[RCBK] },
{ "joy_pad1_mode", rc_joypad, &pad1_mode[RCBJ] },
{ "mou_pad1_mode", rc_mouse, &pad1_mode[RCBM] },
{ "key_pad1_start", rc_keysym, &pad1_start[RCBK] },
{ "joy_pad1_start", rc_joypad, &pad1_start[RCBJ] },
{ "mou_pad1_start", rc_mouse, &pad1_start[RCBM] },
{ "key_pad2_up", rc_keysym, &pad2_up[RCBK] },
{ "joy_pad2_up", rc_joypad, &pad2_up[RCBJ] },
{ "mou_pad2_up", rc_mouse, &pad2_up[RCBM] },
{ "key_pad2_down", rc_keysym, &pad2_down[RCBK] },
{ "joy_pad2_down", rc_joypad, &pad2_down[RCBJ] },
{ "mou_pad2_down", rc_mouse, &pad2_down[RCBM] },
{ "key_pad2_left", rc_keysym, &pad2_left[RCBK] },
{ "joy_pad2_left", rc_joypad, &pad2_left[RCBJ] },
{ "mou_pad2_left", rc_mouse, &pad2_left[RCBM] },
{ "key_pad2_right", rc_keysym, &pad2_right[RCBK] },
{ "joy_pad2_right", rc_joypad, &pad2_right[RCBJ] },
{ "mou_pad2_right", rc_mouse, &pad2_right[RCBM] },
{ "key_pad2_a", rc_keysym, &pad2_a[RCBK] },
{ "joy_pad2_a", rc_joypad, &pad2_a[RCBJ] },
{ "mou_pad2_a", rc_mouse, &pad2_a[RCBM] },
{ "key_pad2_b", rc_keysym, &pad2_b[RCBK] },
{ "joy_pad2_b", rc_joypad, &pad2_b[RCBJ] },
{ "mou_pad2_b", rc_mouse, &pad2_b[RCBM] },
{ "key_pad2_c", rc_keysym, &pad2_c[RCBK] },
{ "joy_pad2_c", rc_joypad, &pad2_c[RCBJ] },
{ "mou_pad2_c", rc_mouse, &pad2_c[RCBM] },
{ "key_pad2_x", rc_keysym, &pad2_x[RCBK] },
{ "joy_pad2_x", rc_joypad, &pad2_x[RCBJ] },
{ "mou_pad2_x", rc_mouse, &pad2_x[RCBM] },
{ "key_pad2_y", rc_keysym, &pad2_y[RCBK] },
{ "joy_pad2_y", rc_joypad, &pad2_y[RCBJ] },
{ "mou_pad2_y", rc_mouse, &pad2_y[RCBM] },
{ "key_pad2_z", rc_keysym, &pad2_z[RCBK] },
{ "joy_pad2_z", rc_joypad, &pad2_z[RCBJ] },
{ "mou_pad2_z", rc_mouse, &pad2_z[RCBM] },
{ "key_pad2_mode", rc_keysym, &pad2_mode[RCBK] },
{ "joy_pad2_mode", rc_joypad, &pad2_mode[RCBJ] },
{ "mou_pad2_mode", rc_mouse, &pad2_mode[RCBM] },
{ "key_pad2_start", rc_keysym, &pad2_start[RCBK] },
{ "joy_pad2_start", rc_joypad, &pad2_start[RCBJ] },
{ "mou_pad2_start", rc_mouse, &pad2_start[RCBM] },
{ "key_pico_pen_up", rc_keysym, &pico_pen_up[RCBK] },
{ "joy_pico_pen_up", rc_joypad, &pico_pen_up[RCBJ] },
{ "mou_pico_pen_up", rc_mouse, &pico_pen_up[RCBM] },
{ "key_pico_pen_down", rc_keysym, &pico_pen_down[RCBK] },
{ "joy_pico_pen_down", rc_joypad, &pico_pen_down[RCBJ] },
{ "mou_pico_pen_down", rc_mouse, &pico_pen_down[RCBM] },
{ "key_pico_pen_left", rc_keysym, &pico_pen_left[RCBK] },
{ "joy_pico_pen_left", rc_joypad, &pico_pen_left[RCBJ] },
{ "mou_pico_pen_left", rc_mouse, &pico_pen_left[RCBM] },
{ "key_pico_pen_right", rc_keysym, &pico_pen_right[RCBK] },
{ "joy_pico_pen_right", rc_joypad, &pico_pen_right[RCBJ] },
{ "mou_pico_pen_right", rc_mouse, &pico_pen_right[RCBM] },
{ "key_pico_pen_button", rc_keysym, &pico_pen_button[RCBK] },
{ "joy_pico_pen_button", rc_joypad, &pico_pen_button[RCBJ] },
{ "mou_pico_pen_button", rc_mouse, &pico_pen_button[RCBM] },
{ "int_pico_pen_stride", rc_number, &pico_pen_stride },
{ "int_pico_pen_delay", rc_number, &pico_pen_delay },
{ "key_fix_checksum", rc_keysym, &dgen_fix_checksum[RCBK] },
{ "joy_fix_checksum", rc_joypad, &dgen_fix_checksum[RCBJ] },
{ "mou_fix_checksum", rc_mouse, &dgen_fix_checksum[RCBM] },
{ "key_quit", rc_keysym, &dgen_quit[RCBK] },
{ "joy_quit", rc_joypad, &dgen_quit[RCBJ] },
{ "mou_quit", rc_mouse, &dgen_quit[RCBM] },
{ "key_craptv_toggle", rc_keysym, &dgen_craptv_toggle[RCBK] },
{ "joy_craptv_toggle", rc_joypad, &dgen_craptv_toggle[RCBJ] },
{ "mou_craptv_toggle", rc_mouse, &dgen_craptv_toggle[RCBM] },
{ "key_scaling_toggle", rc_keysym, &dgen_scaling_toggle[RCBK] },
{ "joy_scaling_toggle", rc_joypad, &dgen_scaling_toggle[RCBJ] },
{ "mou_scaling_toggle", rc_mouse, &dgen_scaling_toggle[RCBM] },
{ "key_screenshot", rc_keysym, &dgen_screenshot[RCBK] },
{ "joy_screenshot", rc_joypad, &dgen_screenshot[RCBJ] },
{ "mou_screenshot", rc_mouse, &dgen_screenshot[RCBM] },
{ "key_reset", rc_keysym, &dgen_reset[RCBK] },
{ "joy_reset", rc_joypad, &dgen_reset[RCBJ] },
{ "mou_reset", rc_mouse, &dgen_reset[RCBM] },
{ "key_slot_0", rc_keysym, &dgen_slot_0[RCBK] },
{ "joy_slot_0", rc_joypad, &dgen_slot_0[RCBJ] },
{ "mou_slot_0", rc_mouse, &dgen_slot_0[RCBM] },
{ "key_slot_1", rc_keysym, &dgen_slot_1[RCBK] },
{ "joy_slot_1", rc_joypad, &dgen_slot_1[RCBJ] },
{ "mou_slot_1", rc_mouse, &dgen_slot_1[RCBM] },
{ "key_slot_2", rc_keysym, &dgen_slot_2[RCBK] },
{ "joy_slot_2", rc_joypad, &dgen_slot_2[RCBJ] },
{ "mou_slot_2", rc_mouse, &dgen_slot_2[RCBM] },
{ "key_slot_3", rc_keysym, &dgen_slot_3[RCBK] },
{ "joy_slot_3", rc_joypad, &dgen_slot_3[RCBJ] },
{ "mou_slot_3", rc_mouse, &dgen_slot_3[RCBM] },
{ "key_slot_4", rc_keysym, &dgen_slot_4[RCBK] },
{ "joy_slot_4", rc_joypad, &dgen_slot_4[RCBJ] },
{ "mou_slot_4", rc_mouse, &dgen_slot_4[RCBM] },
{ "key_slot_5", rc_keysym, &dgen_slot_5[RCBK] },
{ "joy_slot_5", rc_joypad, &dgen_slot_5[RCBJ] },
{ "mou_slot_5", rc_mouse, &dgen_slot_5[RCBM] },
{ "key_slot_6", rc_keysym, &dgen_slot_6[RCBK] },
{ "joy_slot_6", rc_joypad, &dgen_slot_6[RCBJ] },
{ "mou_slot_6", rc_mouse, &dgen_slot_6[RCBM] },
{ "key_slot_7", rc_keysym, &dgen_slot_7[RCBK] },
{ "joy_slot_7", rc_joypad, &dgen_slot_7[RCBJ] },
{ "mou_slot_7", rc_mouse, &dgen_slot_7[RCBM] },
{ "key_slot_8", rc_keysym, &dgen_slot_8[RCBK] },
{ "joy_slot_8", rc_joypad, &dgen_slot_8[RCBJ] },
{ "mou_slot_8", rc_mouse, &dgen_slot_8[RCBM] },
{ "key_slot_9", rc_keysym, &dgen_slot_9[RCBK] },
{ "joy_slot_9", rc_joypad, &dgen_slot_9[RCBJ] },
{ "mou_slot_9", rc_mouse, &dgen_slot_9[RCBM] },
{ "key_slot_next", rc_keysym, &dgen_slot_next[RCBK] },
{ "joy_slot_next", rc_joypad, &dgen_slot_next[RCBJ] },
{ "mou_slot_next", rc_mouse, &dgen_slot_next[RCBM] },
{ "key_slot_prev", rc_keysym, &dgen_slot_prev[RCBK] },
{ "joy_slot_prev", rc_joypad, &dgen_slot_prev[RCBJ] },
{ "mou_slot_prev", rc_mouse, &dgen_slot_prev[RCBM] },
{ "key_save", rc_keysym, &dgen_save[RCBK] },
{ "joy_save", rc_joypad, &dgen_save[RCBJ] },
{ "mou_save", rc_mouse, &dgen_save[RCBM] },
{ "key_load", rc_keysym, &dgen_load[RCBK] },
{ "joy_load", rc_joypad, &dgen_load[RCBJ] },
{ "mou_load", rc_mouse, &dgen_load[RCBM] },
{ "key_z80_toggle", rc_keysym, &dgen_z80_toggle[RCBK] },
{ "joy_z80_toggle", rc_joypad, &dgen_z80_toggle[RCBJ] },
{ "mou_z80_toggle", rc_mouse, &dgen_z80_toggle[RCBM] },
{ "key_cpu_toggle", rc_keysym, &dgen_cpu_toggle[RCBK] },
{ "joy_cpu_toggle", rc_joypad, &dgen_cpu_toggle[RCBJ] },
{ "mou_cpu_toggle", rc_mouse, &dgen_cpu_toggle[RCBM] },
{ "key_stop", rc_keysym, &dgen_stop[RCBK] },
{ "joy_stop", rc_joypad, &dgen_stop[RCBJ] },
{ "mou_stop", rc_mouse, &dgen_stop[RCBM] },
{ "key_game_genie", rc_keysym, &dgen_game_genie[RCBK] },
{ "joy_game_genie", rc_joypad, &dgen_game_genie[RCBJ] },
{ "mou_game_genie", rc_mouse, &dgen_game_genie[RCBM] },
{ "key_fullscreen_toggle", rc_keysym, &dgen_fullscreen_toggle[RCBK] },
{ "joy_fullscreen_toggle", rc_joypad, &dgen_fullscreen_toggle[RCBJ] },
{ "mou_fullscreen_toggle", rc_mouse, &dgen_fullscreen_toggle[RCBM] },
{ "key_debug_enter", rc_keysym, &dgen_debug_enter[RCBK] },
{ "joy_debug_enter", rc_joypad, &dgen_debug_enter[RCBJ] },
{ "mou_debug_enter", rc_mouse, &dgen_debug_enter[RCBM] },
{ "key_prompt", rc_keysym, &dgen_prompt[RCBK] },
{ "joy_prompt", rc_joypad, &dgen_prompt[RCBJ] },
{ "mou_prompt", rc_mouse, &dgen_prompt[RCBM] },
{ "bool_vdp_hide_plane_a", rc_boolean, &dgen_vdp_hide_plane_a },
{ "bool_vdp_hide_plane_b", rc_boolean, &dgen_vdp_hide_plane_b },
{ "bool_vdp_hide_plane_w", rc_boolean, &dgen_vdp_hide_plane_w },
{ "bool_vdp_hide_sprites", rc_boolean, &dgen_vdp_hide_sprites },
{ "bool_vdp_sprites_boxing", rc_boolean, &dgen_vdp_sprites_boxing },
{ "int_vdp_sprites_boxing_fg", rc_number, &dgen_vdp_sprites_boxing_fg },
{ "int_vdp_sprites_boxing_bg", rc_number, &dgen_vdp_sprites_boxing_bg },
{ "bool_autoload", rc_boolean, &dgen_autoload },
{ "bool_autosave", rc_boolean, &dgen_autosave },
{ "bool_autoconf", rc_boolean, &dgen_autoconf },
{ "bool_frameskip", rc_boolean, &dgen_frameskip },
{ "bool_show_carthead", rc_boolean, &dgen_show_carthead },
{ "str_rom_path", rc_rom_path,
(intptr_t *)((void *)&dgen_rom_path) }, // SH
{ "bool_raw_screenshots", rc_boolean, &dgen_raw_screenshots },
{ "ctv_craptv_startup", rc_ctv, &dgen_craptv }, // SH
{ "scaling_startup", rc_scaling, &dgen_scaling }, // SH
{ "emu_z80_startup", rc_emu_z80, &dgen_emu_z80 }, // SH
{ "emu_m68k_startup", rc_emu_m68k, &dgen_emu_m68k }, // SH
{ "bool_sound", rc_boolean, &dgen_sound }, // SH
{ "int_soundrate", rc_soundrate, &dgen_soundrate }, // SH
{ "int_soundsegs", rc_number, &dgen_soundsegs }, // SH
{ "int_soundsamples", rc_number, &dgen_soundsamples }, // SH
{ "int_volume", rc_number, &dgen_volume },
{ "key_volume_inc", rc_keysym, &dgen_volume_inc[RCBK] },
{ "joy_volume_inc", rc_joypad, &dgen_volume_inc[RCBJ] },
{ "mou_volume_inc", rc_mouse, &dgen_volume_inc[RCBM] },
{ "key_volume_dec", rc_keysym, &dgen_volume_dec[RCBK] },
{ "joy_volume_dec", rc_joypad, &dgen_volume_dec[RCBJ] },
{ "mou_volume_dec", rc_mouse, &dgen_volume_dec[RCBM] },
{ "bool_mjazz", rc_boolean, &dgen_mjazz }, // SH
{ "int_nice", rc_number, &dgen_nice },
{ "int_hz", rc_number, &dgen_hz }, // SH
{ "bool_pal", rc_boolean, &dgen_pal }, // SH
{ "region", rc_region, &dgen_region }, // SH
{ "str_region_order", rc_string,
(intptr_t *)((void *)&dgen_region_order) },
{ "bool_fps", rc_boolean, &dgen_fps },
{ "bool_buttons", rc_boolean, &dgen_buttons },
{ "bool_fullscreen", rc_boolean, &dgen_fullscreen }, // SH
{ "int_info_height", rc_number, &dgen_info_height }, // SH
{ "int_width", rc_number, &dgen_width }, // SH
{ "int_height", rc_number, &dgen_height }, // SH
{ "int_scale", rc_number, &dgen_scale }, // SH
{ "int_scale_x", rc_number, &dgen_x_scale }, // SH
{ "int_scale_y", rc_number, &dgen_y_scale }, // SH
{ "int_depth", rc_number, &dgen_depth }, // SH
{ "bool_aspect", rc_boolean, &dgen_aspect }, // SH
{ "bool_swab", rc_boolean, &dgen_swab }, // SH
{ "bool_opengl", rc_boolean, &dgen_opengl }, // SH
{ "bool_opengl_stretch", rc_boolean, &dgen_opengl_stretch }, // SH
// deprecated, use bool_aspect
{ "bool_opengl_aspect", rc_boolean, &dgen_aspect },
// deprecated, use int_width
{ "int_opengl_width", rc_number, &dgen_width },
// deprecated, use int_height
{ "int_opengl_height", rc_number, &dgen_height },
{ "bool_opengl_linear", rc_boolean, &dgen_opengl_linear }, // SH
{ "bool_opengl_32bit", rc_boolean, &dgen_opengl_32bit }, // SH
// deprecated, use bool_swab
{ "bool_opengl_swap", rc_boolean, &dgen_swab }, // SH
{ "bool_opengl_square", rc_boolean, &dgen_opengl_square }, // SH
{ "bool_doublebuffer", rc_boolean, &dgen_doublebuffer }, // SH
{ "bool_screen_thread", rc_boolean, &dgen_screen_thread }, // SH
{ "bool_joystick", rc_boolean, &dgen_joystick }, // SH
{ "int_mouse_delay", rc_number, &dgen_mouse_delay },
{ NULL, NULL, NULL }
};
struct rc_binding rc_binding_head = {
&rc_binding_head,
&rc_binding_head,
{ { false, RCBK, 0, } },
NULL,
NULL
};
static void rc_binding_cleanup(void)
{
struct rc_binding *rcb = rc_binding_head.next;
struct rc_binding *next;
while (rcb != &rc_binding_head) {
next = rcb->next;
assert(rcb->to != NULL);
assert((intptr_t)rcb->to != -1);
free(rcb->to);
#ifndef NDEBUG
memset(rcb, 0x66, sizeof(*rcb));
#endif
free(rcb);
rcb = next;
}
}
struct rc_field *rc_binding_add(const char *rc, const char *to)
{
static bool registered = false;
size_t rc_sz = (strlen(rc) + 1);
struct rc_field *rcf = rc_fields;
struct rc_binding *rcb;
struct rc_binding_item item[elemof(rcb->item)];
unsigned int i;
const char *s;
size_t off;
char *new_to;
if ((registered == false) && (atexit(rc_binding_cleanup) == 0))
registered = true;
// Check if the RC name looks like a valid binding.
for (off = 0; (rc[off] != '\0'); ++off) {
if (RC_BIND_PREFIX[off] == '\0')
break;
if (rc[off] != RC_BIND_PREFIX[off])
return NULL;
}
if (rc[off] == '\0')
return NULL;
// Extract multiple keysyms or joypad codes from RC name.
memset(item, 0, sizeof(item));
s = &rc[off];
i = 0;
while (s += strspn(s, " \t\n"), off = strcspn(s, " \t\n")) {
char tmp[64];
intptr_t code;
enum rc_binding_type type;
if (i == elemof(item))
return NULL;
snprintf(tmp, sizeof(tmp), "%.*s", (int)off, s);
if ((type = RCBJ, ((code = rc_joypad(tmp, NULL)) == -1)) &&
(type = RCBK, ((code = rc_keysym(tmp, NULL)) == -1)) &&
(type = RCBM, ((code = rc_mouse(tmp, NULL)) == -1)))
return NULL;
item[i].assigned = true;
item[i].type = type;
item[i].code = code;
++i;
s += off;
}
// Find a free entry in rc_fields[].
while (rcf->fieldname != NULL)
++rcf;
if ((rcf - rc_fields) == (elemof(rc_fields) - 1))
return NULL;
assert(rcf->parser == NULL);
assert(rcf->variable == NULL);
// Allocate binding.
if ((rcb = (struct rc_binding *)malloc(sizeof(*rcb) + rc_sz)) == NULL)
return NULL;
if ((new_to = strdup(to)) == NULL) {
free(new_to);
free(rcb);
return NULL;
}
// -1 is reserved, thus invalid. Should not happen anyway.
if ((intptr_t)new_to == -1)
abort();
// Configure binding.
rcb->prev = rc_binding_head.prev;
rcb->next = &rc_binding_head;
rcb->prev->next = rcb;
rcb->next->prev = rcb;
memcpy(rcb->item, item, sizeof(rcb->item));
rcb->rc = ((char *)rcb + sizeof(*rcb));
rcb->to = new_to;
memcpy(rcb->rc, rc, rc_sz);
// Configure RC field.
rcf->fieldname = rcb->rc;
rcf->parser = rc_bind;
rcf->variable = (intptr_t *)((void *)&rcb->to);
return rcf;
}
void rc_binding_del(rc_field *rcf)
{
struct rc_binding *rcb =
containerof(rcf->variable, struct rc_binding, to);
assert(rcf >= &rc_fields[0]);
assert(rcf < &rc_fields[elemof(rc_fields)]);
assert(rcf->fieldname != NULL);
assert(rcf->parser != NULL);
assert(rcf->variable != NULL);
if (rcf->parser != rc_bind)
return;
assert(rcb != &rc_binding_head);
// Clean-up.
rcb->prev->next = rcb->next;
rcb->next->prev = rcb->prev;
assert(rcb->to != NULL);
assert((intptr_t)rcb->to != -1);
free(rcb->to);
#ifndef NDEBUG
memset(rcb, 0x88, (sizeof(*rcb) + strlen(rcb->rc) + 1));
#endif
free(rcb);
// Shift the next entries.
do {
memcpy(rcf, (rcf + 1), sizeof(*rcf));
++rcf;
}
while (rcf->fieldname != NULL);
assert(rcf < &rc_fields[elemof(rc_fields)]);
}
intptr_t rc_bind(const char *value, intptr_t *variable)
{
struct rc_binding *rcb = containerof(variable, struct rc_binding, to);
char *to;
assert(*variable != -1);
assert(rcb != NULL);
assert(rcb->prev != NULL);
assert(rcb->next != NULL);
assert(rcb->rc != NULL);
assert(rcb->to != NULL);
assert((intptr_t)rcb->to != -1);
if ((to = strdup(value)) == NULL) {
free(to);
// Get the previous value.
to = rcb->to;
}
// -1 is reserved, thus invalid. Should not happen anyway.
else if ((intptr_t)to == -1)
abort();
else
free(rcb->to);
rcb->to = NULL; // Will be updated by the return value.
// This function must always return a valid pointer.
return (intptr_t)to;
}
/* Replace unprintable characters */
static char *strclean(char *s)
{
size_t i;
for (i = 0; (s[i] != '\0'); ++i)
if (!isprint(s[i]))
s[i] = '?';
return s;
}
/* This is for cleaning up rc_str fields at exit */
struct rc_str *rc_str_list = NULL;
void rc_str_cleanup(void)
{
struct rc_str *rs = rc_str_list;
while (rs != NULL) {
if (rs->val == rs->alloc)
rs->val = NULL;
free(rs->alloc);
rs->alloc = NULL;
rs = rs->next;
}
}
/* Parse the rc file */
void parse_rc(FILE *file, const char *name)
{
struct rc_field *rc_field = NULL;
intptr_t potential;
int overflow = 0;
size_t len;
size_t parse;
ckvp_t ckvp = CKVP_INIT;
char buf[1024];
if ((file == NULL) || (name == NULL))
return;
read:
len = fread(buf, 1, sizeof(buf), file);
/* Check for read errors first */
if ((len == 0) && (ferror(file))) {
fprintf(stderr, "rc: %s: %s\n", name, strerror(errno));
return;
}
/* The goal is to make an extra pass with len == 0 when feof(file) */
parse = 0;
parse:
parse += ckvp_parse(&ckvp, (len - parse), &(buf[parse]));
switch (ckvp.state) {
case CKVP_NONE:
/* Nothing to do */
break;
case CKVP_OUT_FULL:
/*
Buffer is full, field is probably too large. We don't want
to report it more than once, so just store a flag for now.
*/
overflow = 1;
break;
case CKVP_OUT_KEY:
/* Got a key */
if (overflow) {
fprintf(stderr, "rc: %s:%u:%u: key field too large\n",
name, ckvp.line, ckvp.column);
rc_field = NULL;
overflow = 0;
break;
}
/* Find the related rc_field in rc_fields */
assert(ckvp.out_size < sizeof(ckvp.out));
ckvp.out[(ckvp.out_size)] = '\0';
for (rc_field = rc_fields; (rc_field->fieldname != NULL);
++rc_field)
if (!strcasecmp(rc_field->fieldname, ckvp.out))
goto key_over;
/* Try to add it as a new binding. */
if ((rc_field = rc_binding_add(ckvp.out, "")) != NULL)
goto key_over;
fprintf(stderr, "rc: %s:%u:%u: unknown key `%s'\n",
name, ckvp.line, ckvp.column, strclean(ckvp.out));
key_over:
break;
case CKVP_OUT_VALUE:
/* Got a value */
if (overflow) {
fprintf(stderr, "rc: %s:%u:%u: value field too large\n",
name, ckvp.line, ckvp.column);
overflow = 0;
break;
}
assert(ckvp.out_size < sizeof(ckvp.out));
ckvp.out[(ckvp.out_size)] = '\0';
if ((rc_field == NULL) || (rc_field->fieldname == NULL))
break;
potential = rc_field->parser(ckvp.out, rc_field->variable);
/* If we got a bad value, discard and warn user */
if ((rc_field->parser != rc_number) && (potential == -1))
fprintf(stderr,
"rc: %s:%u:%u: invalid value for key"
" `%s': `%s'\n",
name, ckvp.line, ckvp.column,
rc_field->fieldname, strclean(ckvp.out));
else if ((rc_field->parser == rc_string) ||
(rc_field->parser == rc_rom_path)) {
struct rc_str *rs =
(struct rc_str *)rc_field->variable;
if (rc_str_list == NULL) {
atexit(rc_str_cleanup);
rc_str_list = rs;
}
else if (rs->alloc == NULL) {
rs->next = rc_str_list;
rc_str_list = rs;
}
else
free(rs->alloc);
rs->alloc = (char *)potential;
rs->val = rs->alloc;
}
else if ((rc_field->parser == rc_region) &&
(rc_field->variable == &dgen_region)) {
/*
Another special case: updating region also updates
PAL and Hz settings.
*/
*(rc_field->variable) = potential;
if (*(rc_field->variable)) {
int hz;
int pal;
md::region_info(dgen_region, &pal, &hz,
0, 0, 0);
dgen_hz = hz;
dgen_pal = pal;
}
}
else
*(rc_field->variable) = potential;
break;
case CKVP_ERROR:
default:
fprintf(stderr, "rc: %s:%u:%u: syntax error, aborting\n",
name, ckvp.line, ckvp.column);
return;
}
/* Not done with the current buffer? */
if (parse != len)
goto parse;
/* If len != 0, try to read once again */
if (len != 0)
goto read;
}
/* Dump the rc file */
void dump_rc(FILE *file)
{
const struct rc_field *rc = rc_fields;
while (rc->fieldname != NULL) {
intptr_t val = *rc->variable;
char *s = backslashify((const uint8_t *)rc->fieldname,
strlen(rc->fieldname), 0, NULL);
if (s == NULL) {
++rc;
continue;
}
fprintf(file, "%s = ", s);
free(s);
if ((rc->parser == rc_number) ||
(rc->parser == rc_soundrate))
fprintf(file, "%ld", (long)val);
else if (rc->parser == rc_keysym) {
char *ks = dump_keysym(val);
if (ks != NULL) {
fprintf(file, "\"%s\"", ks);
free(ks);
}
else
fputs("''", file);
}
else if (rc->parser == rc_boolean)
fprintf(file, "%s", ((val) ? "true" : "false"));
else if (rc->parser == rc_joypad) {
char *js = dump_joypad(val);
if (js != NULL) {
fprintf(file, "\"%s\"", js);
free(js);
}
else
fputs("''", file);
}
else if (rc->parser == rc_mouse) {
char *mo = dump_mouse(val);
if (mo != NULL) {
fprintf(file, "\"%s\"", mo);
free(mo);
}
else
fputs("''", file);
}
else if (rc->parser == rc_ctv)
fprintf(file, "%s", ctv_names[val]);
else if (rc->parser == rc_scaling)
fprintf(file, "\"%s\"", scaling_names[val]);
else if (rc->parser == rc_emu_z80)
fprintf(file, "%s", emu_z80_names[val]);
else if (rc->parser == rc_emu_m68k)
fprintf(file, "%s", emu_m68k_names[val]);
else if (rc->parser == rc_region) {
if (isgraph((char)val))
fputc((char)val, file);
else
fputs("' '", file);
}
else if ((rc->parser == rc_string) ||
(rc->parser == rc_rom_path)) {
struct rc_str *rs = (struct rc_str *)rc->variable;
if ((rs->val == NULL) ||
((s = backslashify
((const uint8_t *)rs->val,
strlen(rs->val), 0, NULL)) == NULL))
fprintf(file, "\"\"");
else {
fprintf(file, "\"%s\"", s);
free(s);
}
}
else if (rc->parser == rc_bind) {
s = *(char **)rc->variable;
assert(s != NULL);
assert((intptr_t)s != -1);
s = backslashify((uint8_t *)s, strlen(s), 0, NULL);
fputc('"', file);
if (s != NULL) {
fputs(s, file);
free(s);
}
fputc('"', file);
}
fputs("\n", file);
++rc;
}
}