// This parses the RC file. #ifdef __MINGW32__ #undef __STRICT_ANSI__ #endif #include #include #include #include #include #include #include #include #include #include #include #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 * scanline - attenuates every other line, looks cool! by Phillip K. Hornung */ 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; } }