/** * SDL interface */ #ifdef __MINGW32__ #undef __STRICT_ANSI__ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_OPENGL # include #endif #ifdef WITH_THREADS #include #endif #ifdef HAVE_MEMCPY_H #include "memcpy.h" #endif #include "md.h" #include "rc.h" #include "rc-vars.h" #include "pd.h" #include "pd-defs.h" #include "font.h" #include "system.h" #include "prompt.h" #include "romload.h" #include "splash.h" #ifdef WITH_HQX #define HQX_NO_UINT24 #include "hqx.h" #endif #ifdef WITH_SCALE2X extern "C" { #include "scalebit.h" } #endif #ifdef _KOLIBRI #include #endif /// Number of microseconds to sustain messages #define MESSAGE_LIFE 3000000 static void pd_message_process(void); static size_t pd_message_write(const char *msg, size_t len, unsigned int mark); static size_t pd_message_display(const char *msg, size_t len, unsigned int mark, bool update); static void pd_message_postpone(const char *msg); static void pd_message_cursor(unsigned int mark, const char *msg, ...); /// Generic type for supported colour depths. typedef union { uint8_t *u8; uint32_t *u32; uint24_t *u24; uint16_t *u16; uint16_t *u15; } bpp_t; #ifdef WITH_OPENGL /// Framebuffer texture. struct texture { unsigned int width; ///< texture width unsigned int height; ///< texture height unsigned int vis_width; ///< visible width unsigned int vis_height; ///< visible height GLuint id; ///< texture identifier GLuint dlist; ///< display list unsigned int u32:1; ///< texture is 32-bit unsigned int linear:1; ///< linear filtering is enabled union { uint16_t *u16; uint32_t *u32; } buf; ///< 16 or 32-bit buffer }; static void release_texture(struct texture&); static int init_texture(struct screen *); static void update_texture(struct texture&); #endif // WITH_OPENGL struct screen { unsigned int window_width; ///< window width unsigned int window_height; ///< window height unsigned int width; ///< buffer width unsigned int height; ///< buffer height unsigned int bpp; ///< bits per pixel unsigned int Bpp; ///< bytes per pixel unsigned int x_scale; ///< horizontal scale factor unsigned int y_scale; ///< vertical scale factor unsigned int info_height; ///< message bar height (included in height) bpp_t buf; ///< generic pointer to pixel data unsigned int pitch; ///< number of bytes per line in buf SDL_Surface *surface; ///< SDL surface unsigned int want_fullscreen:1; ///< want fullscreen unsigned int is_fullscreen:1; ///< fullscreen enabled #ifdef WITH_OPENGL struct texture texture; ///< OpenGL texture data unsigned int want_opengl:1; ///< want OpenGL unsigned int is_opengl:1; ///< OpenGL enabled #endif #ifdef WITH_THREADS unsigned int want_thread:1; ///< want updates from a separate thread unsigned int is_thread:1; ///< thread is present SDL_Thread *thread; ///< thread itself SDL_mutex *lock; ///< lock for updates SDL_cond *cond; ///< condition variable to signal updates #endif SDL_Color color[64]; ///< SDL colors for 8bpp modes }; static struct screen screen; static struct { const unsigned int width; ///< 320 unsigned int height; ///< 224 or 240 (NTSC_VBLANK or PAL_VBLANK) unsigned int hz; ///< refresh rate unsigned int is_pal: 1; ///< PAL enabled uint8_t palette[256]; ///< palette for 8bpp modes (mdpal) } video = { 320, ///< width is always 320 NTSC_VBLANK, ///< NTSC height by default NTSC_HZ, ///< 60Hz 0, ///< NTSC is enabled { 0 } }; /** * Call this before accessing screen.buf. * No syscalls allowed before screen_unlock(). */ static int screen_lock() { #ifdef WITH_THREADS if (screen.is_thread) { assert(screen.lock != NULL); SDL_LockMutex(screen.lock); } #endif #ifdef WITH_OPENGL if (screen.is_opengl) return 0; #endif if (SDL_MUSTLOCK(screen.surface) == 0) return 0; return SDL_LockSurface(screen.surface); } /** * Call this after accessing screen.buf. */ static void screen_unlock() { #ifdef WITH_THREADS if (screen.is_thread) { assert(screen.lock != NULL); SDL_UnlockMutex(screen.lock); } #endif #ifdef WITH_OPENGL if (screen.is_opengl) return; #endif if (SDL_MUSTLOCK(screen.surface) == 0) return; SDL_UnlockSurface(screen.surface); } /** * Do not call this directly, use screen_update() instead. */ static void screen_update_once() { #ifdef WITH_OPENGL if (screen.is_opengl) { update_texture(screen.texture); return; } #endif SDL_Flip(screen.surface); } #ifdef WITH_THREADS static int screen_update_thread(void *) { assert(screen.lock != NULL); assert(screen.cond != NULL); SDL_LockMutex(screen.lock); while (screen.want_thread) { SDL_CondWait(screen.cond, screen.lock); screen_update_once(); } SDL_UnlockMutex(screen.lock); return 0; } static void screen_update_thread_start() { DEBUG(("starting thread...")); assert(screen.want_thread); assert(screen.lock == NULL); assert(screen.cond == NULL); assert(screen.thread == NULL); #ifdef WITH_OPENGL if (screen.is_opengl) { DEBUG(("this is not supported when OpenGL is enabled")); return; } #endif if ((screen.lock = SDL_CreateMutex()) == NULL) { DEBUG(("unable to create lock")); goto error; } if ((screen.cond = SDL_CreateCond()) == NULL) { DEBUG(("unable to create condition variable")); goto error; } screen.thread = SDL_CreateThread(screen_update_thread, NULL); if (screen.thread == NULL) { DEBUG(("unable to start thread")); goto error; } screen.is_thread = 1; DEBUG(("thread started")); return; error: if (screen.cond != NULL) { SDL_DestroyCond(screen.cond); screen.cond = NULL; } if (screen.lock != NULL) { SDL_DestroyMutex(screen.lock); screen.lock = NULL; } } static void screen_update_thread_stop() { if (!screen.is_thread) { assert(screen.thread == NULL); return; } DEBUG(("stopping thread...")); assert(screen.thread != NULL); screen.want_thread = 0; SDL_CondSignal(screen.cond); SDL_WaitThread(screen.thread, NULL); screen.thread = NULL; SDL_DestroyCond(screen.cond); screen.cond = NULL; SDL_DestroyMutex(screen.lock); screen.lock = NULL; screen.is_thread = 0; DEBUG(("thread stopped")); } #endif // WITH_THREADS /** * Call this after writing into screen.buf. */ static void screen_update() { #ifdef WITH_THREADS if (screen.is_thread) SDL_CondSignal(screen.cond); else #endif // WITH_THREADS screen_update_once(); } /** * Clear screen. */ static void screen_clear() { if ((screen.buf.u8 == NULL) || (screen_lock())) return; memset(screen.buf.u8, 0, (screen.pitch * screen.height)); screen_unlock(); } // Bad hack- extern slot etc. from main.cpp so we can save/load states extern int slot; void md_save(md &megad); void md_load(md &megad); // Define externed variables struct bmap mdscr; unsigned char *mdpal = NULL; struct sndinfo sndi; const char *pd_options = #ifdef WITH_OPENGL "g:" #endif "fX:Y:S:G:"; static void mdscr_splash(); /// Circular buffer and related functions. typedef struct { size_t i; ///< data start index size_t s; ///< data size size_t size; ///< buffer size union { uint8_t *u8; int16_t *i16; } data; ///< storage } cbuf_t; /** * Write/copy data into a circular buffer. * @param[in,out] cbuf Destination buffer. * @param[in] src Buffer to copy from. * @param size Size of src. * @return Number of bytes copied. */ size_t cbuf_write(cbuf_t *cbuf, uint8_t *src, size_t size) { size_t j; size_t k; if (size > cbuf->size) { src += (size - cbuf->size); size = cbuf->size; } k = (cbuf->size - cbuf->s); j = ((cbuf->i + cbuf->s) % cbuf->size); if (size > k) { cbuf->i = ((cbuf->i + (size - k)) % cbuf->size); cbuf->s = cbuf->size; } else cbuf->s += size; k = (cbuf->size - j); if (k >= size) { memcpy(&cbuf->data.u8[j], src, size); } else { memcpy(&cbuf->data.u8[j], src, k); memcpy(&cbuf->data.u8[0], &src[k], (size - k)); } return size; } /** * Read bytes out of a circular buffer. * @param[out] dst Destination buffer. * @param[in,out] cbuf Circular buffer to read from. * @param size Maximum number of bytes to copy to dst. * @return Number of bytes copied. */ size_t cbuf_read(uint8_t *dst, cbuf_t *cbuf, size_t size) { if (size > cbuf->s) size = cbuf->s; if ((cbuf->i + size) > cbuf->size) { size_t k = (cbuf->size - cbuf->i); memcpy(&dst[0], &cbuf->data.u8[(cbuf->i)], k); memcpy(&dst[k], &cbuf->data.u8[0], (size - k)); } else memcpy(&dst[0], &cbuf->data.u8[(cbuf->i)], size); cbuf->i = ((cbuf->i + size) % cbuf->size); cbuf->s -= size; return size; } /// Sound static struct { unsigned int rate; ///< samples rate unsigned int samples; ///< number of samples required by the callback cbuf_t cbuf; ///< circular buffer } sound; /// Messages static struct { unsigned int displayed:1; ///< whether message is currently displayed unsigned long since; ///< since this number of microseconds size_t length; ///< remaining length to display char message[2048]; ///< message } info; /// Prompt static struct { struct prompt status; ///< prompt status char** complete; ///< completion results array unsigned int skip; ///< number of entries to skip in the array unsigned int common; ///< common length of all entries } prompt; /// Prompt return values #define PROMPT_RET_CONT 0x01 ///< waiting for more input #define PROMPT_RET_EXIT 0x02 ///< leave prompt normally #define PROMPT_RET_ERROR 0x04 ///< leave prompt with error #define PROMPT_RET_ENTER 0x10 ///< previous line entered #define PROMPT_RET_MSG 0x80 ///< pd_message() has been used struct prompt_command { const char* name; /// command function pointer int (*cmd)(class md&, unsigned int, const char**); /// completion function shoud complete the last entry in the array char* (*cmpl)(class md&, unsigned int, const char**, unsigned int); }; // Extra commands usable from prompt. static int prompt_cmd_exit(class md&, unsigned int, const char**); static int prompt_cmd_load(class md&, unsigned int, const char**); static char* prompt_cmpl_load(class md&, unsigned int, const char**, unsigned int); static int prompt_cmd_unload(class md&, unsigned int, const char**); static int prompt_cmd_reset(class md&, unsigned int, const char**); static int prompt_cmd_unbind(class md&, unsigned int, const char**); static char* prompt_cmpl_unbind(class md&, unsigned int, const char**, unsigned int); #ifdef WITH_CTV static int prompt_cmd_filter_push(class md&, unsigned int, const char**); static char* prompt_cmpl_filter_push(class md&, unsigned int, const char**, unsigned int); static int prompt_cmd_filter_pop(class md&, unsigned int, const char**); static int prompt_cmd_filter_none(class md&, unsigned int, const char**); #endif static int prompt_cmd_calibrate(class md&, unsigned int, const char**); static int prompt_cmd_config_load(class md&, unsigned int, const char**); static int prompt_cmd_config_save(class md&, unsigned int, const char**); static char* prompt_cmpl_config_file(class md&, unsigned int, const char**, unsigned int); #ifdef WITH_VGMDUMP static char* prompt_cmpl_vgmdump(class md&, unsigned int, const char**, unsigned int); static int prompt_cmd_vgmdump(class md&, unsigned int, const char**); #endif /** * List of commands to auto complete. */ static const struct prompt_command prompt_command[] = { { "quit", prompt_cmd_exit, NULL }, { "q", prompt_cmd_exit, NULL }, { "exit", prompt_cmd_exit, NULL }, { "load", prompt_cmd_load, prompt_cmpl_load }, { "open", prompt_cmd_load, prompt_cmpl_load }, { "o", prompt_cmd_load, prompt_cmpl_load }, { "plug", prompt_cmd_load, prompt_cmpl_load }, { "unload", prompt_cmd_unload, NULL }, { "close", prompt_cmd_unload, NULL }, { "unplug", prompt_cmd_unload, NULL }, { "reset", prompt_cmd_reset, NULL }, { "unbind", prompt_cmd_unbind, prompt_cmpl_unbind }, #ifdef WITH_CTV { "ctv_push", prompt_cmd_filter_push, prompt_cmpl_filter_push }, { "ctv_pop", prompt_cmd_filter_pop, NULL }, { "ctv_none", prompt_cmd_filter_none, NULL }, #endif { "calibrate", prompt_cmd_calibrate, NULL }, { "calibrate_js", prompt_cmd_calibrate, NULL }, // deprecated name { "config_load", prompt_cmd_config_load, prompt_cmpl_config_file }, { "config_save", prompt_cmd_config_save, prompt_cmpl_config_file }, #ifdef WITH_VGMDUMP { "vgmdump", prompt_cmd_vgmdump, prompt_cmpl_vgmdump }, #endif { NULL, NULL, NULL } }; /// Extra commands return values. #define CMD_OK 0x00 ///< command successful #define CMD_EINVAL 0x01 ///< invalid argument #define CMD_FAIL 0x02 ///< command failed #define CMD_ERROR 0x03 ///< fatal error, DGen should exit #define CMD_MSG 0x80 ///< pd_message() has been used /// Stopped flag used by pd_stopped() static int stopped = 0; /// Events handling status. static enum events { STARTED, STOPPED, STOPPED_PROMPT, STOPPED_GAME_GENIE, PROMPT, GAME_GENIE } events = STARTED; static int stop_events(md& megad, enum events status); static void restart_events(md &megad); /// Messages shown whenever events are stopped. static const char stopped_str[] = "STOPPED."; static const char prompt_str[] = ":"; static const char game_genie_str[] = "Enter Game Genie/Hex code: "; /// Enable emulation by default. bool pd_freeze = false; static unsigned int pd_freeze_ref = 0; static void freeze(bool toggle) { if (toggle == true) { if (!pd_freeze_ref) { assert(pd_freeze == false); pd_freeze = true; } pd_freeze_ref++; } else { if (pd_freeze_ref) { assert(pd_freeze == true); pd_freeze_ref--; if (!pd_freeze_ref) pd_freeze = false; } else assert(pd_freeze == false); } } /** * Elapsed time in microseconds. * @return Microseconds. */ unsigned long pd_usecs(void) { #ifndef _KOLIBRI struct timeval tv; gettimeofday(&tv, NULL); return (unsigned long)((tv.tv_sec * 1000000) + tv.tv_usec); #else return _ksys_get_tick_count()*10000; #endif } /** * Prompt "exit" command handler. * @return Error status to make DGen exit. */ static int prompt_cmd_exit(class md&, unsigned int, const char**) { return (CMD_ERROR | CMD_MSG); } /** * Prompt "load" command handler. * @param md Context. * @param ac Number of arguments in av. * @param av Arguments. * @return Status code. */ static int prompt_cmd_load(class md& md, unsigned int ac, const char** av) { extern int slot; extern void ram_save(class md&); extern void ram_load(class md&); char *s; if (ac != 2) return CMD_EINVAL; s = backslashify((const uint8_t *)av[1], strlen(av[1]), 0, NULL); if (s == NULL) return CMD_FAIL; ram_save(md); if (dgen_autosave) { slot = 0; md_save(md); } md.unplug(); pd_message(""); if (md.load(av[1])) { mdscr_splash(); pd_message("Unable to load \"%s\"", s); free(s); return (CMD_FAIL | CMD_MSG); } pd_message("Loaded \"%s\"", s); free(s); if (dgen_show_carthead) pd_show_carthead(md); // Initialize like main() does. md.reset(); if (!dgen_region) { uint8_t c = md.region_guess(); int hz; int pal; md::region_info(c, &pal, &hz, 0, 0, 0); if ((hz != dgen_hz) || (pal != dgen_pal) || (c != md.region)) { md.region = c; dgen_hz = hz; dgen_pal = pal; printf("sdl: reconfiguring for region \"%c\": " "%dHz (%s)\n", c, hz, (pal ? "PAL" : "NTSC")); pd_graphics_reinit(dgen_sound, dgen_pal, dgen_hz); if (dgen_sound) { long rate = dgen_soundrate; unsigned int samples; pd_sound_deinit(); samples = (dgen_soundsegs * (rate / dgen_hz)); pd_sound_init(rate, samples); } md.pal = pal; md.init_pal(); md.init_sound(); } } ram_load(md); if (dgen_autoload) { slot = 0; md_load(md); } return (CMD_OK | CMD_MSG); } static void rehash_prompt_complete_common() { size_t i; unsigned int common; prompt.common = 0; if ((prompt.complete == NULL) || (prompt.complete[0] == NULL)) return; common = strlen(prompt.complete[0]); for (i = 1; (prompt.complete[i] != NULL); ++i) { unsigned int tmp; tmp = strcommon(prompt.complete[i], prompt.complete[(i - 1)]); if (tmp < common) common = tmp; if (common == 0) break; } prompt.common = common; } static char* prompt_cmpl_load(class md& md, unsigned int ac, const char** av, unsigned int len) { const char *prefix; size_t i; unsigned int skip; (void)md; assert(ac != 0); if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) { prefix = ""; len = 0; } else prefix = av[(ac - 1)]; if (prompt.complete == NULL) { // Rebuild cache. prompt.skip = 0; prompt.complete = complete_path(prefix, len, dgen_rom_path.val); if (prompt.complete == NULL) return NULL; rehash_prompt_complete_common(); } retry: skip = prompt.skip; for (i = 0; (prompt.complete[i] != NULL); ++i) { if (skip == 0) break; --skip; } if (prompt.complete[i] == NULL) { if (prompt.skip != 0) { prompt.skip = 0; goto retry; } return NULL; } ++prompt.skip; return strdup(prompt.complete[i]); } static int prompt_cmd_unload(class md& md, unsigned int, const char**) { extern int slot; extern void ram_save(class md&); info.length = 0; // clear postponed messages pd_message("No cartridge."); ram_save(md); if (dgen_autosave) { slot = 0; md_save(md); } if (md.unplug()) return (CMD_FAIL | CMD_MSG); mdscr_splash(); return (CMD_OK | CMD_MSG); } static char* prompt_cmpl_config_file(class md& md, unsigned int ac, const char** av, unsigned int len) { const char *prefix; size_t i; unsigned int skip; (void)md; assert(ac != 0); if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) { prefix = ""; len = 0; } else prefix = av[(ac - 1)]; if (prompt.complete == NULL) { // Rebuild cache. prompt.skip = 0; prompt.complete = complete_path(prefix, len, "config"); if (prompt.complete == NULL) return NULL; rehash_prompt_complete_common(); } retry: skip = prompt.skip; for (i = 0; (prompt.complete[i] != NULL); ++i) { if (skip == 0) break; --skip; } if (prompt.complete[i] == NULL) { if (prompt.skip != 0) { prompt.skip = 0; goto retry; } return NULL; } ++prompt.skip; return strdup(prompt.complete[i]); } static int prompt_rehash_rc_field(const struct rc_field*, md&); /** * Prompt "config_load" command handler. * @param md Context. * @param ac Number of arguments in av. * @param av Arguments. * @return Status code. */ static int prompt_cmd_config_load(class md& md, unsigned int ac, const char** av) { FILE *f; char *s; unsigned int i; if (ac != 2) return CMD_EINVAL; s = backslashify((const uint8_t *)av[1], strlen(av[1]), 0, NULL); if (s == NULL) return CMD_FAIL; f = dgen_fopen("config", av[1], (DGEN_READ | DGEN_CURRENT)); if (f == NULL) { pd_message("Cannot load configuration \"%s\": %s.", s, strerror(errno)); free(s); return (CMD_FAIL | CMD_MSG); } parse_rc(f, av[1]); fclose(f); for (i = 0; (rc_fields[i].fieldname != NULL); ++i) prompt_rehash_rc_field(&rc_fields[i], md); pd_message("Loaded configuration \"%s\".", s); free(s); return (CMD_OK | CMD_MSG); } /** * Prompt "config_save" command handler. * @param md Context. * @param ac Number of arguments in av. * @param av Arguments. * @return Status code. */ static int prompt_cmd_config_save(class md& md, unsigned int ac, const char** av) { FILE *f; char *s; (void)md; if (ac != 2) return CMD_EINVAL; s = backslashify((const uint8_t *)av[1], strlen(av[1]), 0, NULL); if (s == NULL) return CMD_FAIL; f = dgen_fopen("config", av[1], (DGEN_WRITE | DGEN_TEXT)); if (f == NULL) { pd_message("Cannot save configuration \"%s\": %s", s, strerror(errno)); free(s); return (CMD_FAIL | CMD_MSG); } dump_rc(f); fclose(f); pd_message("Saved configuration \"%s\"", s); free(s); return (CMD_OK | CMD_MSG); } static int prompt_cmd_reset(class md& md, unsigned int, const char**) { md.reset(); return CMD_OK; } static int prompt_cmd_unbind(class md&, unsigned int ac, const char** av) { unsigned int i; int ret; if (ac < 2) return CMD_EINVAL; ret = CMD_FAIL; for (i = 1; (i != ac); ++i) { struct rc_field *rcf = rc_fields; while (rcf->fieldname != NULL) { if ((rcf->parser == rc_bind) && (!strcasecmp(av[i], rcf->fieldname))) { rc_binding_del(rcf); ret = CMD_OK; } else ++rcf; } } return ret; } static char* prompt_cmpl_unbind(class md&, unsigned int ac, const char** av, unsigned int len) { const struct rc_binding *rcb; const char *prefix; unsigned int skip; assert(ac != 0); if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) { prefix = ""; len = 0; } else prefix = av[(ac - 1)]; skip = prompt.skip; retry: for (rcb = rc_binding_head.next; (rcb != &rc_binding_head); rcb = rcb->next) { if (strncasecmp(prefix, rcb->rc, len)) continue; if (skip == 0) break; --skip; } if (rcb == &rc_binding_head) { if (prompt.skip != 0) { prompt.skip = 0; goto retry; } return NULL; } ++prompt.skip; return strdup(rcb->rc); } #ifdef WITH_VGMDUMP static char* prompt_cmpl_vgmdump(class md& md, unsigned int ac, const char** av, unsigned int len) { const char *prefix; size_t i; unsigned int skip; (void)md; assert(ac != 0); if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) { prefix = ""; len = 0; } else prefix = av[(ac - 1)]; if (prompt.complete == NULL) { // Rebuild cache. prompt.skip = 0; prompt.complete = complete_path(prefix, len, "vgm"); if (prompt.complete == NULL) return NULL; rehash_prompt_complete_common(); } retry: skip = prompt.skip; for (i = 0; (prompt.complete[i] != NULL); ++i) { if (skip == 0) break; --skip; } if (prompt.complete[i] == NULL) { if (prompt.skip != 0) { prompt.skip = 0; goto retry; } return NULL; } ++prompt.skip; return strdup(prompt.complete[i]); } static int prompt_cmd_vgmdump(class md& md, unsigned int ac, const char** av) { char *s; if (ac < 2) return CMD_EINVAL; if (!strcasecmp(av[1], "stop")) { if (md.vgm_dump == false) pd_message("VGM dumping already stopped."); else { md.vgm_dump_stop(); pd_message("Stopped VGM dumping."); } return (CMD_OK | CMD_MSG); } if (strcasecmp(av[1], "start")) return CMD_EINVAL; if (ac < 3) { pd_message("VGM file name required."); return (CMD_EINVAL | CMD_MSG); } s = backslashify((const uint8_t *)av[2], strlen(av[2]), 0, NULL); if (s == NULL) return CMD_FAIL; if (md.vgm_dump_start(av[2])) { pd_message("Cannot dump VGM to \"%s\": %s", s, strerror(errno)); free(s); return (CMD_FAIL | CMD_MSG); } pd_message("Started VGM dumping to \"%s\"", s); free(s); return (CMD_OK | CMD_MSG); } #endif struct filter_data { bpp_t buf; ///< Input or output buffer. unsigned int width; ///< Buffer width. unsigned int height; ///< Buffer height. unsigned int pitch; ///< Number of bytes per line in buffer. void *data; ///< Filter-specific data. bool updated:1; ///< Filter updated data to match its output. bool failed:1; ///< Filter failed. }; typedef void filter_func_t(const struct filter_data *in, struct filter_data *out); struct filter { const char *name; ///< Filter name. filter_func_t *func; ///< Filtering function. bool safe:1; ///< Output buffer can be the same as input. bool ctv:1; ///< Part of the CTV filters set. bool resize:1; ///< Filter resizes input. }; static filter_func_t filter_scale; static filter_func_t filter_off; static filter_func_t filter_stretch; #ifdef WITH_SCALE2X static filter_func_t filter_scale2x; #endif #ifdef WITH_HQX static filter_func_t filter_hqx; #endif #ifdef WITH_CTV static filter_func_t filter_blur; static filter_func_t filter_scanline; static filter_func_t filter_interlace; static filter_func_t filter_swab; static void set_swab(); #endif static const struct filter filters_available[] = { { "stretch", filter_stretch, false, false, true }, { "scale", filter_scale, false, false, true }, #ifdef WITH_SCALE2X { "scale2x", filter_scale2x, false, false, true }, #endif #ifdef WITH_HQX { "hqx", filter_hqx, false, false, true }, #endif { "none", filter_off, true, false, true }, #ifdef WITH_CTV // These filters must match ctv_names in rc.cpp. { "off", filter_off, true, true, false }, { "blur", filter_blur, true, true, false }, { "scanline", filter_scanline, true, true, false }, { "interlace", filter_interlace, true, true, false }, { "swab", filter_swab, true, true, false }, #endif }; static unsigned int filters_stack_size; static bool filters_stack_default; static const struct filter *filters_stack[64]; static bpp_t filters_stack_data_buf[2]; static struct filter_data filters_stack_data[1 + elemof(filters_stack)]; /** * Return filter structure associated with name. * @param name Name of filter. * @return Pointer to filter or NULL if not found. */ static const struct filter *filters_find(const char *name) { size_t i; for (i = 0; (i != elemof(filters_available)); ++i) if (!strcasecmp(name, filters_available[i].name)) return &filters_available[i]; return NULL; } /** * Update filters data, reallocate extra buffers if necessary. */ static void filters_stack_update() { size_t i; const struct filter *f; struct filter_data *fd; unsigned int buffers; bpp_t buf[2]; struct filter_data in_fd = { // Use the same formula as draw_scanline() in ras.cpp to avoid // the messy border for any supported depth. { ((uint8_t *)mdscr.data + (mdscr.pitch * 8) + 16) }, video.width, video.height, (unsigned int)mdscr.pitch, NULL, false, false, }; struct filter_data out_fd = { { screen.buf.u8 }, screen.width, (screen.height - screen.info_height), screen.pitch, NULL, false, false, }; struct filter_data *prev_fd; DEBUG(("updating filters data")); retry: assert(filters_stack_size <= elemof(filters_stack)); buffers = 0; buf[0].u8 = filters_stack_data_buf[0].u8; buf[1].u8 = filters_stack_data_buf[1].u8; // Get the number of defined filters and count how many of them cannot // use the same buffer for both input and output. // Unless they are on top, "unsafe" filters require extra buffers. assert(filters_stack_data[0].data == NULL); for (i = 0; (i != elemof(filters_stack)); ++i) { if (i == filters_stack_size) break; f = filters_stack[i]; assert(f != NULL); if ((f->safe == false) && (i != (filters_stack_size - 1))) ++buffers; // Clear filters stack output data. free(filters_stack_data[i + 1].data); } memset(filters_stack_data, 0, sizeof(filters_stack_data)); // Add a valid default filter if stack is empty. if (i == 0) { assert(filters_stack_size == 0); filters_stack[0] = &filters_available[0]; ++filters_stack_size; filters_stack_default = true; goto retry; } // Remove default filter if there is one and stack is not empty. else if ((i > 1) && (filters_stack_default == true)) { assert(filters_stack_size > 1); --filters_stack_size; memmove(&filters_stack[0], &filters_stack[1], (sizeof(filters_stack[0]) * filters_stack_size)); filters_stack_default = false; goto retry; } // Check if extra buffers are required. if (buffers) { if (buffers > 2) buffers = 2; else { // Remove unnecessary buffer. free(buf[1].u8); buf[1].u8 = NULL; filters_stack_data_buf[1].u8 = NULL; } DEBUG(("requiring %u extra buffer(s)", buffers)); // Reallocate them. for (i = 0; (i != buffers); ++i) { size_t size = (screen.pitch * screen.height); DEBUG(("temporary buffer %u size: %zu", i, size)); buf[i].u8 = (uint8_t *)realloc((void *)buf[i].u8, size); if (size == 0) { assert(buf[i].u8 == NULL); DEBUG(("freed zero-sized buffer")); filters_stack_data_buf[i].u8 = NULL; continue; } if (buf[i].u8 == NULL) { // Not good, remove one of the filters that // require an extra buffer and try again. free(filters_stack_data_buf[i].u8); filters_stack_data_buf[i].u8 = NULL; for (i = 0; (i < filters_stack_size); ++i) { if (filters_stack[i]->safe == true) continue; --filters_stack_size; memmove(&filters_stack[i], &filters_stack[i + 1], (sizeof(filters_stack[i]) * (filters_stack_size - i))); break; } goto retry; } filters_stack_data_buf[i].u8 = buf[i].u8; } } else { // No extra buffer required, deallocate them. DEBUG(("removing temporary buffers")); for (i = 0; (i != elemof(buf)); ++i) { free(buf[i].u8); buf[i].u8 = NULL; filters_stack_data_buf[i].u8 = NULL; } } // Update I/O buffers. buffers = 0; prev_fd = &filters_stack_data[0]; memcpy(prev_fd, &in_fd, sizeof(*prev_fd)); for (i = 0; (i != elemof(filters_stack)); ++i) { if (i == filters_stack_size) break; f = filters_stack[i]; fd = &filters_stack_data[i + 1]; // The last filter uses screen output. if (i == (filters_stack_size - 1)) memcpy(fd, &out_fd, sizeof(*fd)); // Safe filters have the same input as their output. else if (f->safe == true) memcpy(fd, prev_fd, sizeof(*fd)); // Other filters output to a temporary buffer. else { fd->buf.u8 = buf[buffers].u8; fd->width = screen.width; fd->height = (screen.height - screen.info_height); fd->pitch = screen.pitch; fd->data = NULL; fd->updated = false; fd->failed = false; buffers ^= 1; } prev_fd = fd; } #ifndef NDEBUG DEBUG(("filters stack:")); for (i = 0; (i != filters_stack_size); ++i) DEBUG(("- %s (input: %p output: %p)", filters_stack[i]->name, (void *)filters_stack_data[i].buf.u8, (void *)filters_stack_data[i + 1].buf.u8)); #endif screen_clear(); } /** * Add filter to stack. * @param f Filter to add. */ static void filters_push(const struct filter *f) { assert(filters_stack_size <= elemof(filters_stack)); if ((f == NULL) || (filters_stack_size == elemof(filters_stack))) return; DEBUG(("%s", f->name)); filters_stack[filters_stack_size] = f; filters_stack_data[filters_stack_size + 1].data = NULL; ++filters_stack_size; filters_stack_update(); } /** * Insert filter at the bottom of the stack. * @param f Filter to insert. */ static void filters_insert(const struct filter *f) { assert(filters_stack_size <= elemof(filters_stack)); if ((f == NULL) || (filters_stack_size == elemof(filters_stack))) return; DEBUG(("%s", f->name)); memmove(&filters_stack[1], &filters_stack[0], (filters_stack_size * sizeof(filters_stack[0]))); filters_stack[0] = f; filters_stack_data[0 + 1].data = NULL; ++filters_stack_size; filters_stack_update(); } // Currently unused. #if 0 /** * Add filter to stack if not already in it. * @param f Filter to add. */ static void filters_push_once(const struct filter *f) { size_t i; assert(filters_stack_size <= elemof(filters_stack)); if (f == NULL) return; DEBUG(("%s", f->name)); for (i = 0; (i != filters_stack_size); ++i) if (filters_stack[i] == f) return; filters_push(f); } #endif #ifdef WITH_CTV /** * Remove last filter from stack. */ static void filters_pop() { assert(filters_stack_size <= elemof(filters_stack)); if (filters_stack_size) { --filters_stack_size; DEBUG(("%s", filters_stack[filters_stack_size]->name)); free(filters_stack_data[filters_stack_size + 1].data); #ifndef NDEBUG memset(&filters_stack[filters_stack_size], 0xf0, sizeof(filters_stack[filters_stack_size])); memset(&filters_stack_data[filters_stack_size + 1], 0xf1, sizeof(filters_stack_data[filters_stack_size + 1])); #endif } filters_stack_update(); } #endif /** * Remove a filter from anywhere in the stack. * @param index Filters stack index. */ static void filters_remove(unsigned int index) { assert(filters_stack_size <= elemof(filters_stack)); if (index >= filters_stack_size) return; --filters_stack_size; DEBUG(("%s", filters_stack[index]->name)); free(filters_stack_data[index + 1].data); #ifndef NDEBUG memset(&filters_stack[index], 0xf2, sizeof(filters_stack[index])); memset(&filters_stack_data[index + 1], 0xf3, sizeof(filters_stack_data[index + 1])); #endif memmove(&filters_stack[index], &filters_stack[index + 1], (sizeof(filters_stack[index]) * (filters_stack_size - index))); memmove(&filters_stack_data[index + 1], &filters_stack_data[index + 2], (sizeof(filters_stack_data[index + 1]) * (filters_stack_size - index))); filters_stack_update(); } /** * Remove all occurences of filter from the stack. * @param f Filter to remove. */ static void filters_pluck(const struct filter *f) { size_t i; assert(filters_stack_size <= elemof(filters_stack)); if (f == NULL) return; DEBUG(("%s", f->name)); for (i = 0; (i < filters_stack_size); ++i) { if (filters_stack[i] != f) continue; --filters_stack_size; DEBUG(("%s", filters_stack[i]->name)); free(filters_stack_data[i + 1].data); #ifndef NDEBUG memset(&filters_stack[i], 0xf4, sizeof(filters_stack[i])); memset(&filters_stack_data[i + 1], 0xf5, sizeof(filters_stack_data[i + 1])); #endif memmove(&filters_stack[i], &filters_stack[i + 1], (sizeof(filters_stack[i]) * (filters_stack_size - i))); memmove(&filters_stack_data[i + 1], &filters_stack_data[i + 2], (sizeof(filters_stack_data[i + 1]) * (filters_stack_size - i))); --i; } filters_stack_update(); } #ifdef WITH_CTV /** * Remove all occurences of CTV filters from the stack. */ static void filters_pluck_ctv() { size_t i; assert(filters_stack_size <= elemof(filters_stack)); for (i = 0; (i < filters_stack_size); ++i) { if (filters_stack[i]->ctv == false) continue; --filters_stack_size; DEBUG(("%s", filters_stack[i]->name)); free(filters_stack_data[i + 1].data); #ifndef NDEBUG memset(&filters_stack[i], 0xf6, sizeof(filters_stack[i])); memset(&filters_stack_data[i + 1], 0xf6, sizeof(filters_stack_data[i + 1])); #endif memmove(&filters_stack[i], &filters_stack[i + 1], (sizeof(filters_stack[i]) * (filters_stack_size - i))); memmove(&filters_stack_data[i + 1], &filters_stack_data[i + 2], (sizeof(filters_stack_data[i + 1]) * (filters_stack_size - i))); --i; } filters_stack_update(); } #endif #ifdef WITH_CTV /** * Empty filters stack. */ static void filters_empty() { size_t i; assert(filters_stack_size <= elemof(filters_stack)); DEBUG(("stack size was %u", filters_stack_size)); for (i = 0; (i < filters_stack_size); ++i) free(filters_stack_data[i + 1].data); filters_stack_size = 0; #ifndef NDEBUG memset(filters_stack, 0xb0, sizeof(filters_stack)); memset(filters_stack_data, 0xb0, sizeof(filters_stack_data)); filters_stack_data[0].data = NULL; #endif filters_stack_update(); } #endif /** * Take a screenshot. */ static void do_screenshot(md& megad) { static unsigned int n = 0; static char romname_old[sizeof(megad.romname)]; FILE *fp; #ifdef HAVE_FTELLO off_t pos; #else long pos; #endif bpp_t line; unsigned int width; unsigned int height; unsigned int pitch; unsigned int bpp = mdscr.bpp; uint8_t (*out)[3]; // 24 bpp char name[(sizeof(megad.romname) + 32)]; if (dgen_raw_screenshots) { width = video.width; height = video.height; pitch = mdscr.pitch; line.u8 = ((uint8_t *)mdscr.data + (pitch * 8) + 16); } else { width = screen.width; height = screen.height; pitch = screen.pitch; line = screen.buf; } switch (bpp) { case 15: case 16: case 24: case 32: break; default: pd_message("Screenshots unsupported in %d bpp.", bpp); return; } // Make take a long time, let the main loop know about it. stopped = 1; // If megad.romname is different from last time, reset n. if (memcmp(romname_old, megad.romname, sizeof(romname_old))) { memcpy(romname_old, megad.romname, sizeof(romname_old)); n = 0; } retry: snprintf(name, sizeof(name), "%s-%06u.tga", ((megad.romname[0] == '\0') ? "unknown" : megad.romname), n); fp = dgen_fopen("screenshots", name, DGEN_APPEND); if (fp == NULL) { pd_message("Can't open %s.", name); return; } fseek(fp, 0, SEEK_END); #ifdef HAVE_FTELLO pos = ftello(fp); #else pos = ftell(fp); #endif if (((off_t)pos == (off_t)-1) || ((off_t)pos != (off_t)0)) { fclose(fp); n = ((n + 1) % 1000000); goto retry; } // Allocate line buffer. if ((out = (uint8_t (*)[3])malloc(sizeof(*out) * width)) == NULL) goto error; // Header { uint8_t tmp[(3 + 5)] = { 0x00, // length of the image ID field 0x00, // whether a color map is included 0x02 // image type: uncompressed, true-color image // 5 bytes of color map specification }; if (!fwrite(tmp, sizeof(tmp), 1, fp)) goto error; } { uint16_t tmp[4] = { 0, // x-origin 0, // y-origin h2le16(width), // width h2le16(height) // height }; if (!fwrite(tmp, sizeof(tmp), 1, fp)) goto error; } { uint8_t tmp[2] = { 24, // always output 24 bits per pixel (1 << 5) // top-left origin }; if (!fwrite(tmp, sizeof(tmp), 1, fp)) goto error; } // Data switch (bpp) { unsigned int y; unsigned int x; case 15: for (y = 0; (y < height); ++y) { if (screen_lock()) goto error; for (x = 0; (x < width); ++x) { uint16_t v = line.u16[x]; out[x][0] = ((v << 3) & 0xf8); out[x][1] = ((v >> 2) & 0xf8); out[x][2] = ((v >> 7) & 0xf8); } screen_unlock(); if (!fwrite(out, (sizeof(*out) * width), 1, fp)) goto error; line.u8 += pitch; } break; case 16: for (y = 0; (y < height); ++y) { if (screen_lock()) goto error; for (x = 0; (x < width); ++x) { uint16_t v = line.u16[x]; out[x][0] = ((v << 3) & 0xf8); out[x][1] = ((v >> 3) & 0xfc); out[x][2] = ((v >> 8) & 0xf8); } screen_unlock(); if (!fwrite(out, (sizeof(*out) * width), 1, fp)) goto error; line.u8 += pitch; } break; case 24: for (y = 0; (y < height); ++y) { if (screen_lock()) goto error; #ifdef WORDS_BIGENDIAN for (x = 0; (x < width); ++x) { out[x][0] = line.u24[x][2]; out[x][1] = line.u24[x][1]; out[x][2] = line.u24[x][0]; } #else memcpy(out, line.u24, (sizeof(*out) * width)); #endif screen_unlock(); if (!fwrite(out, (sizeof(*out) * width), 1, fp)) goto error; line.u8 += pitch; } break; case 32: for (y = 0; (y < height); ++y) { if (screen_lock()) goto error; for (x = 0; (x < width); ++x) { #ifdef WORDS_BIGENDIAN uint32_t rgb = h2le32(line.u32[x]); memcpy(&(out[x]), &rgb, 3); #else memcpy(&(out[x]), &(line.u32[x]), 3); #endif } screen_unlock(); if (!fwrite(out, (sizeof(*out) * width), 1, fp)) goto error; line.u8 += pitch; } break; } pd_message("Screenshot written to %s.", name); free(out); fclose(fp); return; error: pd_message("Error while generating screenshot %s.", name); free(out); fclose(fp); } /** * SDL flags help. */ void pd_help() { printf( #ifdef WITH_OPENGL " -g (1|0) Enable/disable OpenGL.\n" #endif " -f Attempt to run fullscreen.\n" " -X scale Scale the screen in the X direction.\n" " -Y scale Scale the screen in the Y direction.\n" " -S scale Scale the screen by the same amount in both directions.\n" " -G WxH Desired window size.\n" ); } /** * Handle rc variables */ void pd_rc() { // Set stuff up from the rcfile first, so we can override it with // command-line options if (dgen_scale >= 1) { dgen_x_scale = dgen_scale; dgen_y_scale = dgen_scale; } #ifdef WITH_CTV set_swab(); #endif } /** * Handle the switches. * @param c Switch's value. */ void pd_option(char c, const char *) { int xs, ys; switch (c) { #ifdef WITH_OPENGL case 'g': dgen_opengl = atoi(optarg); break; #endif case 'f': dgen_fullscreen = 1; break; case 'X': if ((xs = atoi(optarg)) <= 0) break; dgen_x_scale = xs; break; case 'Y': if ((ys = atoi(optarg)) <= 0) break; dgen_y_scale = ys; break; case 'S': if ((xs = atoi(optarg)) <= 0) break; dgen_x_scale = xs; dgen_y_scale = xs; break; case 'G': if ((sscanf(optarg, " %d x %d ", &xs, &ys) != 2) || (xs < 0) || (ys < 0)) break; dgen_width = xs; dgen_height = ys; break; } } #ifdef WITH_OPENGL #ifdef WORDS_BIGENDIAN #define TEXTURE_16_TYPE GL_UNSIGNED_SHORT_5_6_5 #define TEXTURE_32_TYPE GL_UNSIGNED_INT_8_8_8_8_REV #else #define TEXTURE_16_TYPE GL_UNSIGNED_SHORT_5_6_5 #define TEXTURE_32_TYPE GL_UNSIGNED_BYTE #endif static void texture_init_id(struct texture& texture) { GLint param; if (texture.linear) param = GL_LINEAR; else param = GL_NEAREST; glBindTexture(GL_TEXTURE_2D, texture.id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, param); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, param); if (texture.u32 == 0) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture.width, texture.height, 0, GL_RGB, TEXTURE_16_TYPE, texture.buf.u16); else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture.width, texture.height, 0, GL_BGRA, TEXTURE_32_TYPE, texture.buf.u32); } static void texture_init_dlist(struct texture& texture) { glNewList(texture.dlist, GL_COMPILE); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glOrtho(0, texture.vis_width, texture.vis_height, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBindTexture(GL_TEXTURE_2D, texture.id); glBegin(GL_QUADS); glTexCoord2i(0, 1); glVertex2i(0, texture.height); // lower left glTexCoord2i(0, 0); glVertex2i(0, 0); // upper left glTexCoord2i(1, 0); glVertex2i(texture.width, 0); // upper right glTexCoord2i(1, 1); glVertex2i(texture.width, texture.height); // lower right glEnd(); glDisable(GL_TEXTURE_2D); glPopMatrix(); glEndList(); } /** * Round a value up to nearest power of two. * @param v Value. * @return Rounded value. */ static uint32_t roundup2(uint32_t v) { --v; v |= (v >> 1); v |= (v >> 2); v |= (v >> 4); v |= (v >> 8); v |= (v >> 16); ++v; return v; } static void release_texture(struct texture& texture) { if ((texture.dlist != 0) && (glIsList(texture.dlist))) { glDeleteTextures(1, &texture.id); glDeleteLists(texture.dlist, 1); texture.dlist = 0; } free(texture.buf.u32); texture.buf.u32 = NULL; } static int init_texture(struct screen *screen) { struct texture& texture = screen->texture; unsigned int vis_width; unsigned int vis_height; unsigned int x; unsigned int y; unsigned int w; unsigned int h; void *tmp; size_t i; GLenum error; // When bool_opengl_stretch is enabled, width and height are redefined // using X and Y scale factors with additional room for the info bar. if (dgen_opengl_stretch) { vis_width = (video.width * (screen->x_scale ? screen->x_scale : 1)); vis_height = (video.height * (screen->y_scale ? screen->y_scale : 1)); vis_height += screen->info_height; if (dgen_aspect) { // Keep scaled aspect ratio. w = ((screen->height * vis_width) / vis_height); h = ((screen->width * vis_height) / vis_width); if (w >= screen->width) { w = screen->width; if (h == 0) ++h; } else { h = screen->height; if (w == 0) ++w; } } else { // Aspect ratio won't be kept. w = screen->width; h = screen->height; } } else { w = vis_width = screen->width; h = vis_height = screen->height; } if (screen->width > w) x = ((screen->width - w) / 2); else x = 0; if (screen->height > h) y = ((screen->height - h) / 2); else y = 0; DEBUG(("initializing for width=%u height=%u", vis_width, vis_height)); // Set viewport. DEBUG(("glViewport(%u, %u, %u, %u)", x, y, w, h)); glViewport(x, y, w, h); // Disable dithering glDisable(GL_DITHER); // Disable anti-aliasing glDisable(GL_LINE_SMOOTH); glDisable(GL_POINT_SMOOTH); // Disable depth buffer glDisable(GL_DEPTH_TEST); glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_FLAT); // Initialize and allocate texture. texture.u32 = (!!dgen_opengl_32bit); texture.linear = (!!dgen_opengl_linear); texture.width = roundup2(vis_width); texture.height = roundup2(vis_height); if (dgen_opengl_square) { // Texture must be square. if (texture.width < texture.height) texture.width = texture.height; else texture.height = texture.width; } texture.vis_width = vis_width; texture.vis_height = vis_height; DEBUG(("texture width=%u height=%u", texture.width, texture.height)); if ((texture.width == 0) || (texture.height == 0)) goto fail; i = ((texture.width * texture.height) * (2 << texture.u32)); DEBUG(("texture size=%lu (%u Bpp)", (unsigned long)i, (2 << texture.u32))); if ((tmp = realloc(texture.buf.u32, i)) == NULL) goto fail; memset(tmp, 0, i); texture.buf.u32 = (uint32_t *)tmp; if ((texture.dlist != 0) && (glIsList(texture.dlist))) { glDeleteTextures(1, &texture.id); glDeleteLists(texture.dlist, 1); } DEBUG(("texture buf=%p", (void *)texture.buf.u32)); if ((texture.dlist = glGenLists(1)) == 0) goto fail; if ((glGenTextures(1, &texture.id), error = glGetError()) || (texture_init_id(texture), error = glGetError()) || (texture_init_dlist(texture), error = glGetError())) { // Do something with "error". goto fail; } DEBUG(("texture initialization OK")); return 0; fail: release_texture(texture); DEBUG(("texture initialization failed")); return -1; } static void update_texture(struct texture& texture) { glBindTexture(GL_TEXTURE_2D, texture.id); if (texture.u32 == 0) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.vis_width, texture.vis_height, GL_RGB, TEXTURE_16_TYPE, texture.buf.u16); else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.vis_width, texture.vis_height, GL_BGRA, TEXTURE_32_TYPE, texture.buf.u32); glCallList(texture.dlist); SDL_GL_SwapBuffers(); } #endif // WITH_OPENGL /** * This filter passes input to output unchanged, only centered or truncated * if necessary. Doesn't have any fallback, thus cannot fail. * @param in Input buffer data. * @param out Output buffer data. */ static void filter_off(const struct filter_data *in, struct filter_data *out) { unsigned int line; unsigned int height; uint8_t *in_buf; uint8_t *out_buf; // Check if copying is necessary. if (in->buf.u8 == out->buf.u8) return; // Copy line by line and center. if (in->height > out->height) height = out->height; else height = in->height; if (out->updated == false) { if (in->width <= out->width) { unsigned int x_off = ((out->width - in->width) / 2); unsigned int y_off = ((out->height - height) / 2); out->buf.u8 += (x_off * screen.Bpp); out->buf.u8 += (out->pitch * y_off); out->width = in->width; } out->height = height; out->updated = true; } in_buf = in->buf.u8; out_buf = out->buf.u8; for (line = 0; (line != height); ++line) { memcpy(out_buf, in_buf, (out->width * screen.Bpp)); in_buf += in->pitch; out_buf += out->pitch; } } // Copy/rescale functions. struct filter_scale_data { unsigned int x_scale; unsigned int y_scale; filter_func_t *filter; }; template static void filter_scale_X(const struct filter_data *in, struct filter_data *out) { struct filter_scale_data *data = (struct filter_scale_data *)out->data; uintX_t *dst = (uintX_t *)out->buf.u32; unsigned int dst_pitch = out->pitch; uintX_t *src = (uintX_t *)in->buf.u32; unsigned int src_pitch = in->pitch; unsigned int width = in->width; unsigned int x_scale = data->x_scale; unsigned int y_scale = data->y_scale; unsigned int height = in->height; unsigned int y; for (y = 0; (y != height); ++y) { uintX_t *out = dst; unsigned int i; unsigned int x; for (x = 0; (x != width); ++x) { uintX_t tmp = src[x]; for (i = 0; (i != x_scale); ++i) *(out++) = tmp; } out = dst; dst = (uintX_t *)((uint8_t *)dst + dst_pitch); for (i = 1; (i < y_scale); ++i) { memcpy(dst, out, (width * sizeof(*dst) * x_scale)); out = dst; dst = (uintX_t *)((uint8_t *)dst + dst_pitch); } src = (uintX_t *)((uint8_t *)src + src_pitch); } } static void filter_scale_3(const struct filter_data *in, struct filter_data *out) { struct filter_scale_data *data = (struct filter_scale_data *)out->data; uint24_t *dst = out->buf.u24; unsigned int dst_pitch = out->pitch; uint24_t *src = in->buf.u24; unsigned int src_pitch = in->pitch; unsigned int width = in->width; unsigned int x_scale = data->x_scale; unsigned int y_scale = data->y_scale; unsigned int height = in->height; unsigned int y; for (y = 0; (y != height); ++y) { uint24_t *out = dst; unsigned int i; unsigned int x; for (x = 0; (x != width); ++x) { uint24_t tmp; u24cpy(&tmp, &src[x]); for (i = 0; (i != x_scale); ++i) u24cpy((out++), &tmp); } out = dst; dst = (uint24_t *)((uint8_t *)dst + dst_pitch); for (i = 1; (i < y_scale); ++i) { memcpy(dst, out, (width * sizeof(*dst) * x_scale)); out = dst; dst = (uint24_t *)((uint8_t *)dst + dst_pitch); } src = (uint24_t *)((uint8_t *)src + src_pitch); } } /** * This filter attempts to rescale according to screen X/Y factors. * @param in Input buffer data. * @param out Output buffer data. */ static void filter_scale(const struct filter_data *in, struct filter_data *out) { static const struct { unsigned int Bpp; filter_func_t *func; } scale_mode[] = { { 1, filter_scale_X }, { 2, filter_scale_X }, { 3, filter_scale_3 }, { 4, filter_scale_X }, }; struct filter_scale_data *data; unsigned int width; unsigned int height; unsigned int x_off; unsigned int y_off; unsigned int x_scale; unsigned int y_scale; filter_func_t *filter; unsigned int i; if (out->failed == true) { failed: filter_off(in, out); return; } if (out->updated == true) { data = (struct filter_scale_data *)out->data; filter = data->filter; process: // Feed this to the basic scaler. (*filter)(in, out); return; } // Initialize filter. assert(out->data == NULL); x_scale = screen.x_scale; y_scale = screen.y_scale; while ((width = (in->width * x_scale)) > out->width) --x_scale; while ((height = (in->height * y_scale)) > out->height) --y_scale; // Check whether output is large enough. if ((x_scale == 0) || (y_scale == 0)) { DEBUG(("cannot rescale by %ux%u", x_scale, y_scale)); out->failed = true; goto failed; } // Not rescaling is faster through filter_off(). if ((x_scale == 1) && (y_scale == 1)) { DEBUG(("using faster fallback for %ux%u", x_scale, y_scale)); out->failed = true; goto failed; } // Find a suitable filter. for (i = 0; (i != elemof(scale_mode)); ++i) if (scale_mode[i].Bpp == screen.Bpp) break; if (i == elemof(scale_mode)) { DEBUG(("%u Bpp depth is not supported", screen.Bpp)); out->failed = true; goto failed; } DEBUG(("using %u Bpp function to scale by %ux%u", screen.Bpp, x_scale, y_scale)); data = (struct filter_scale_data *)malloc(sizeof(*data)); if (data == NULL) { DEBUG(("allocation failure")); out->failed = true; goto failed; } filter = scale_mode[i].func; data->filter = filter; data->x_scale = x_scale; data->y_scale = y_scale; // Center output. x_off = ((out->width - width) / 2); y_off = ((out->height - height) / 2); out->buf.u8 += (x_off * screen.Bpp); out->buf.u8 += (out->pitch * y_off); out->width = width; out->height = height; out->data = (void *)data; out->updated = true; goto process; } struct filter_stretch_data { uint8_t *h_table; uint8_t *v_table; filter_func_t *filter; }; template static void filter_stretch_X(const struct filter_data *in, struct filter_data *out) { struct filter_stretch_data *data = (struct filter_stretch_data *)out->data; uint8_t *h_table = data->h_table; uint8_t *v_table = data->v_table; uintX_t *dst = (uintX_t *)out->buf.u8; unsigned int dst_pitch = out->pitch; unsigned int dst_w = out->width; uintX_t *src = (uintX_t *)in->buf.u8; unsigned int src_pitch = in->pitch; unsigned int src_w = in->width; unsigned int src_h = in->height; unsigned int src_y; dst_pitch /= sizeof(*dst); src_pitch /= sizeof(*src); for (src_y = 0; (src_y != src_h); ++src_y) { uint8_t v_repeat = v_table[src_y]; unsigned int src_x; unsigned int dst_x; if (!v_repeat) { src += src_pitch; continue; } for (src_x = 0, dst_x = 0; (src_x != src_w); ++src_x) { uint8_t h_repeat = h_table[src_x]; if (!h_repeat) continue; while (h_repeat--) dst[dst_x++] = src[src_x]; } dst += dst_pitch; while (--v_repeat) { memcpy(dst, (dst - dst_pitch), (dst_w * sizeof(*dst))); dst += dst_pitch; } src += src_pitch; } } static void filter_stretch_3(const struct filter_data *in, struct filter_data *out) { struct filter_stretch_data *data = (struct filter_stretch_data *)out->data; uint8_t *h_table = data->h_table; uint8_t *v_table = data->v_table; uint24_t *dst = out->buf.u24; unsigned int dst_pitch = out->pitch; unsigned int dst_w = out->width; uint24_t *src = in->buf.u24; unsigned int src_pitch = in->pitch; unsigned int src_w = in->width; unsigned int src_h = in->height; unsigned int src_y; dst_pitch /= sizeof(*dst); src_pitch /= sizeof(*src); for (src_y = 0; (src_y != src_h); ++src_y) { uint8_t v_repeat = v_table[src_y]; unsigned int src_x; unsigned int dst_x; if (!v_repeat) { src += src_pitch; continue; } for (src_x = 0, dst_x = 0; (src_x != src_w); ++src_x) { uint8_t h_repeat = h_table[src_x]; if (!h_repeat) continue; while (h_repeat--) u24cpy(&dst[dst_x++], &src[src_x]); } dst += dst_pitch; while (--v_repeat) { memcpy(dst, (dst - dst_pitch), (dst_w * sizeof(*dst))); dst += dst_pitch; } src += src_pitch; } } /** * This filter stretches the input buffer to fill the entire output. * @param in Input buffer data. * @param out Output buffer data. */ static void filter_stretch(const struct filter_data *in, struct filter_data *out) { static const struct { unsigned int Bpp; filter_func_t *func; } stretch_mode[] = { { 1, filter_stretch_X }, { 2, filter_stretch_X }, { 3, filter_stretch_3 }, { 4, filter_stretch_X }, }; struct filter_stretch_data *data; unsigned int dst_w; unsigned int dst_h; unsigned int src_w; unsigned int src_h; unsigned int h_ratio; unsigned int v_ratio; unsigned int dst_x; unsigned int dst_y; unsigned int src_x; unsigned int src_y; filter_func_t *filter; unsigned int i; if (out->failed == true) { failed: filter_off(in, out); return; } if (out->updated == true) { data = (struct filter_stretch_data *)out->data; filter = data->filter; process: (*filter)(in, out); return; } // Initialize filter. assert(out->data == NULL); dst_w = out->width; dst_h = out->height; src_w = in->width; src_h = in->height; if ((src_h == 0) || (src_w == 0)) { DEBUG(("invalid input size: %ux%u", src_h, src_w)); out->failed = true; goto failed; } // Make sure input and output pitches are multiples of pixel size // at the current depth. if ((in->pitch % screen.Bpp) || (out->pitch % screen.Bpp)) { DEBUG(("Bpp: %u, in->pitch: %u, out->pitch: %u", screen.Bpp, in->pitch, out->pitch)); out->failed = true; goto failed; } // Find a suitable filter. for (i = 0; (i != elemof(stretch_mode)); ++i) if (stretch_mode[i].Bpp == screen.Bpp) break; if (i == elemof(stretch_mode)) { DEBUG(("%u Bpp depth is not supported", screen.Bpp)); out->failed = true; goto failed; } filter = stretch_mode[i].func; // Fix output if original aspect ratio must be kept. if (dgen_aspect) { unsigned int w = ((dst_h * src_w) / src_h); unsigned int h = ((dst_w * src_h) / src_w); if (w >= dst_w) { w = dst_w; if (h == 0) ++h; } else { h = dst_h; if (w == 0) ++w; } dst_w = w; dst_h = h; } // Precompute H and V pixel ratios. h_ratio = ((dst_w << 10) / src_w); v_ratio = ((dst_h << 10) / src_h); data = (struct filter_stretch_data *) calloc(1, sizeof(*data) + src_w + src_h); if (data == NULL) { DEBUG(("allocation failure")); out->failed = true; goto failed; } DEBUG(("stretching %ux%u to %ux%u/%ux%u (aspect ratio %s)", src_w, src_h, dst_w, dst_h, out->width, out->height, (dgen_aspect ? "must be kept" : "is free"))); data->h_table = (uint8_t *)(data + 1); data->v_table = (data->h_table + src_w); data->filter = filter; for (dst_x = 0; (dst_x != dst_w); ++dst_x) { src_x = ((dst_x << 10) / h_ratio); if (src_x < src_w) ++data->h_table[src_x]; } for (dst_y = 0; (dst_y != dst_h); ++dst_y) { src_y = ((dst_y << 10) / v_ratio); if (src_y < src_h) ++data->v_table[src_y]; } // Center output. dst_x = ((out->width - dst_w) / 2); dst_y = ((out->height - dst_h) / 2); out->buf.u8 += (dst_x * screen.Bpp); out->buf.u8 += (out->pitch * dst_y); out->width = dst_w; out->height = dst_h; out->data = (void *)data; out->updated = true; goto process; } #ifdef WITH_HQX /** * This filter attempts to rescale with HQX. * @param in Input buffer data. * @param out Output buffer data. */ static void filter_hqx(const struct filter_data *in, struct filter_data *out) { typedef void hqx_func_t(void *src, uint32_t src_pitch, void *dst, uint32_t dst_pitch, int width, int height); static const struct { unsigned int Bpp; unsigned int scale; hqx_func_t *func; } hqx_mode[] = { { 2, 2, (hqx_func_t *)hq2x_16_rb }, { 2, 3, (hqx_func_t *)hq3x_16_rb }, { 2, 4, (hqx_func_t *)hq4x_16_rb }, { 3, 2, (hqx_func_t *)hq2x_24_rb }, { 3, 3, (hqx_func_t *)hq3x_24_rb }, { 3, 4, (hqx_func_t *)hq4x_24_rb }, { 4, 2, (hqx_func_t *)hq2x_32_rb }, { 4, 3, (hqx_func_t *)hq3x_32_rb }, { 4, 4, (hqx_func_t *)hq4x_32_rb }, }; static bool hqx_initialized = false; unsigned int width; unsigned int height; unsigned int x_off; unsigned int y_off; unsigned int x_scale; unsigned int y_scale; hqx_func_t *hqx; unsigned int i; if (out->failed == true) { failed: filter_off(in, out); return; } if (out->updated == true) { hqx = *(hqx_func_t **)out->data; process: // Feed this to HQX. (*hqx)((void *)in->buf.u32, in->pitch, (void *)out->buf.u32, out->pitch, in->width, in->height); return; } // Initialize filter. assert(out->data == NULL); x_scale = screen.x_scale; y_scale = screen.y_scale; retry: while ((width = (in->width * x_scale)) > out->width) --x_scale; while ((height = (in->height * y_scale)) > out->height) --y_scale; // Check whether output is large enough. if ((x_scale == 0) || (y_scale == 0)) { DEBUG(("cannot rescale by %ux%u", x_scale, y_scale)); out->failed = true; goto failed; } // Find a suitable combination. for (i = 0; (i != elemof(hqx_mode)); ++i) if ((hqx_mode[i].Bpp == screen.Bpp) && (hqx_mode[i].scale == x_scale) && (hqx_mode[i].scale == y_scale)) break; if (i == elemof(hqx_mode)) { // Nothing matches, find something that fits. DEBUG(("%ux%u @%u Bpp scale factor not supported, trying" " another", x_scale, y_scale, screen.Bpp)); // Must be square. if (x_scale > y_scale) x_scale = y_scale; else if (y_scale > x_scale) y_scale = x_scale; assert(x_scale == y_scale); (void)y_scale; do { --i; if ((hqx_mode[i].Bpp == screen.Bpp) && (hqx_mode[i].scale <= x_scale)) { x_scale = hqx_mode[i].scale; y_scale = hqx_mode[i].scale; goto retry; } } while (i != 0); DEBUG(("failed to use %ux%u @%u Bpp scale factor", x_scale, y_scale, screen.Bpp)); out->failed = true; goto failed; } DEBUG(("using %ux%u @%u Bpp scale factor", x_scale, y_scale, screen.Bpp)); hqx = hqx_mode[i].func; out->data = malloc(sizeof(hqx)); if (out->data == NULL) { DEBUG(("allocation failure")); out->failed = true; goto failed; } *(hqx_func_t **)out->data = hqx; // Center output. x_off = ((out->width - width) / 2); y_off = ((out->height - height) / 2); out->buf.u8 += (x_off * screen.Bpp); out->buf.u8 += (out->pitch * y_off); out->width = width; out->height = height; out->updated = true; // Initialize HQX if necessary. if (hqx_initialized == false) { pd_message_cursor(~0u, "Initializing hqx..."); stopped = 1; hqxInit(); pd_message_cursor(~0u, ""); hqx_initialized = true; } goto process; } #endif // WITH_HQX #ifdef WITH_SCALE2X /** * This filter attempts to rescale with Scale2x. * @param in Input buffer data. * @param out Output buffer data. */ static void filter_scale2x(const struct filter_data *in, struct filter_data *out) { static const struct { unsigned int x_scale; unsigned int y_scale; unsigned int mode; } scale2x_mode[] = { { 2, 2, 2 }, { 2, 3, 203 }, { 2, 4, 204 }, { 3, 3, 3 }, { 4, 4, 4 } }; unsigned int width; unsigned int height; unsigned int x_off; unsigned int y_off; unsigned int x_scale; unsigned int y_scale; unsigned int mode; unsigned int i; if (out->failed == true) { failed: filter_off(in, out); return; } if (out->updated == true) { mode = *(unsigned int *)out->data; process: // Feed this to scale2x. scale(mode, out->buf.u32, out->pitch, in->buf.u32, in->pitch, screen.Bpp, in->width, in->height); return; } // Initialize filter. assert(out->data == NULL); x_scale = screen.x_scale; y_scale = screen.y_scale; retry: while ((width = (in->width * x_scale)) > out->width) --x_scale; while ((height = (in->height * y_scale)) > out->height) --y_scale; // Check whether output is large enough. if ((x_scale == 0) || (y_scale == 0)) { DEBUG(("cannot rescale by %ux%u", x_scale, y_scale)); out->failed = true; goto failed; } // Check whether depth is supported by filter. if ((screen.Bpp != 4) && (screen.Bpp != 2)) { DEBUG(("unsupported depth %u", screen.bpp)); out->failed = true; goto failed; } // Find a suitable combination. for (i = 0; (i != elemof(scale2x_mode)); ++i) if ((scale2x_mode[i].x_scale == x_scale) && (scale2x_mode[i].y_scale == y_scale)) break; if (i == elemof(scale2x_mode)) { // Nothing matches, find something that fits. DEBUG(("%ux%u scale factor not supported, trying another", x_scale, y_scale)); do { --i; if ((scale2x_mode[i].x_scale <= x_scale) && (scale2x_mode[i].y_scale <= y_scale)) { x_scale = scale2x_mode[i].x_scale; y_scale = scale2x_mode[i].y_scale; goto retry; } } while (i != 0); DEBUG(("failed to use %ux%u scale factor", x_scale, y_scale)); out->failed = true; goto failed; } DEBUG(("using %ux%u scale factor", x_scale, y_scale)); mode = scale2x_mode[i].mode; out->data = malloc(sizeof(mode)); if (out->data == NULL) { DEBUG(("allocation failure")); out->failed = true; goto failed; } *(unsigned int *)out->data = mode; // Center output. x_off = ((out->width - width) / 2); y_off = ((out->height - height) / 2); out->buf.u8 += (x_off * screen.Bpp); out->buf.u8 += (out->pitch * y_off); out->width = width; out->height = height; out->updated = true; goto process; } #endif // WITH_SCALE2X #ifdef WITH_CTV // "Blur" CTV filters. static void filter_blur_32(const struct filter_data *in, struct filter_data *out) { bpp_t in_buf = in->buf; bpp_t out_buf = out->buf; unsigned int xsize = out->width; unsigned int ysize = out->height; unsigned int y; for (y = 0; (y < ysize); ++y) { uint32_t old = *in_buf.u32; unsigned int x; for (x = 0; (x < xsize); ++x) { uint32_t tmp = in_buf.u32[x]; tmp = (((((tmp & 0x00ff00ff) + (old & 0x00ff00ff)) >> 1) & 0x00ff00ff) | ((((tmp & 0xff00ff00) + (old & 0xff00ff00)) >> 1) & 0xff00ff00)); old = in_buf.u32[x]; out_buf.u32[x] = tmp; } in_buf.u8 += in->pitch; out_buf.u8 += out->pitch; } } static void filter_blur_24(const struct filter_data *in, struct filter_data *out) { bpp_t in_buf = in->buf; bpp_t out_buf = out->buf; unsigned int xsize = out->width; unsigned int ysize = out->height; unsigned int y; for (y = 0; (y < ysize); ++y) { uint24_t old; unsigned int x; u24cpy(&old, in_buf.u24); for (x = 0; (x < xsize); ++x) { uint24_t tmp; u24cpy(&tmp, &in_buf.u24[x]); out_buf.u24[x][0] = ((tmp[0] + old[0]) >> 1); out_buf.u24[x][1] = ((tmp[1] + old[1]) >> 1); out_buf.u24[x][2] = ((tmp[2] + old[2]) >> 1); u24cpy(&old, &tmp); } in_buf.u8 += in->pitch; out_buf.u8 += out->pitch; } } static void filter_blur_16(const struct filter_data *in, struct filter_data *out) { bpp_t in_buf = in->buf; bpp_t out_buf = out->buf; unsigned int xsize = out->width; unsigned int ysize = out->height; unsigned int y; #ifdef WITH_X86_CTV if (in_buf.u16 == out_buf.u16) { for (y = 0; (y < ysize); ++y) { // Blur, by Dave blur_bitmap_16((uint8_t *)out_buf.u16, (xsize - 1)); out_buf.u8 += out->pitch; } return; } #endif for (y = 0; (y < ysize); ++y) { uint16_t old = *in_buf.u16; unsigned int x; for (x = 0; (x < xsize); ++x) { uint16_t tmp = in_buf.u16[x]; tmp = (((((tmp & 0xf81f) + (old & 0xf81f)) >> 1) & 0xf81f) | ((((tmp & 0x07e0) + (old & 0x07e0)) >> 1) & 0x07e0)); old = in_buf.u16[x]; out_buf.u16[x] = tmp; } in_buf.u8 += in->pitch; out_buf.u8 += out->pitch; } } static void filter_blur_15(const struct filter_data *in, struct filter_data *out) { bpp_t in_buf = in->buf; bpp_t out_buf = out->buf; unsigned int xsize = out->width; unsigned int ysize = out->height; unsigned int y; #ifdef WITH_X86_CTV if (in_buf.u15 == out_buf.u15) { for (y = 0; (y < ysize); ++y) { // Blur, by Dave blur_bitmap_15((uint8_t *)out_buf.u15, (xsize - 1)); out_buf.u8 += out->pitch; } return; } #endif for (y = 0; (y < ysize); ++y) { uint16_t old = *in_buf.u15; unsigned int x; for (x = 0; (x < xsize); ++x) { uint16_t tmp = in_buf.u15[x]; tmp = (((((tmp & 0x7c1f) + (old & 0x7c1f)) >> 1) & 0x7c1f) | ((((tmp & 0x03e0) + (old & 0x03e0)) >> 1) & 0x03e0)); old = in_buf.u15[x]; out_buf.u15[x] = tmp; } in_buf.u8 += in->pitch; out_buf.u8 += out->pitch; } } static void filter_blur(const struct filter_data *in, struct filter_data *out) { static const struct { unsigned int bpp; filter_func_t *filter; } blur_mode[] = { { 32, filter_blur_32 }, { 24, filter_blur_24 }, { 16, filter_blur_16 }, { 15, filter_blur_15 }, }; filter_func_t *blur; if (out->failed == true) { failed: filter_off(in, out); return; } if (out->updated == false) { unsigned int i; for (i = 0; (i != elemof(blur_mode)); ++i) if (blur_mode[i].bpp == screen.bpp) break; if (i == elemof(blur_mode)) { DEBUG(("%u bpp depth is not supported", screen.bpp)); out->failed = true; goto failed; } blur = blur_mode[i].filter; out->data = malloc(sizeof(filter)); if (out->data == NULL) { DEBUG(("allocation failure")); out->failed = true; goto failed; } if (in->width <= out->width) { unsigned int x_off = ((out->width - in->width) / 2); unsigned int y_off = ((out->height - in->height) / 2); out->buf.u8 += (x_off * screen.Bpp); out->buf.u8 += (out->pitch * y_off); out->width = in->width; } if (in->height <= out->height) out->height = in->height; *((filter_func_t **)out->data) = blur; out->updated = true; } else blur = *(filter_func_t **)out->data; (*blur)(in, out); } // Scanline/Interlace CTV filters. static void filter_scanline_frame(const struct filter_data *in, struct filter_data *out) { unsigned int frame = ((unsigned int *)out->data)[0]; unsigned int bpp = ((unsigned int *)out->data)[1]; bpp_t in_buf = in->buf; bpp_t out_buf = out->buf; unsigned int xsize = out->width; unsigned int ysize = out->height; out_buf.u8 += (out->pitch * !!frame); switch (bpp) { unsigned int x; unsigned int y; case 32: for (y = frame; (y < ysize); y += 2) { for (x = 0; (x < xsize); ++x) out_buf.u32[x] = ((in_buf.u32[x] >> 1) & 0x7f7f7f7f); in_buf.u8 += (in->pitch * 2); out_buf.u8 += (out->pitch * 2); } break; case 24: for (y = frame; (y < ysize); y += 2) { for (x = 0; (x < xsize); ++x) { out_buf.u24[x][0] = (in_buf.u24[x][0] >> 1); out_buf.u24[x][1] = (in_buf.u24[x][1] >> 1); out_buf.u24[x][2] = (in_buf.u24[x][2] >> 1); } in_buf.u8 += (in->pitch * 2); out_buf.u8 += (out->pitch * 2); } break; case 16: for (y = frame; (y < ysize); y += 2) { #ifdef WITH_X86_CTV if (in_buf.u16 == out_buf.u16) { // Scanline, by Phil test_ctv((uint8_t *)out_buf.u16, xsize); } else #endif for (x = 0; (x < xsize); ++x) out_buf.u16[x] = ((in_buf.u16[x] >> 1) & 0x7bef); in_buf.u8 += (in->pitch * 2); out_buf.u8 += (out->pitch * 2); } break; case 15: for (y = frame; (y < ysize); y += 2) { #ifdef WITH_X86_CTV if (in_buf.u15 == out_buf.u15) { // Scanline, by Phil test_ctv((uint8_t *)out_buf.u16, xsize); } else #endif for (x = 0; (x < xsize); ++x) out_buf.u15[x] = ((in_buf.u15[x] >> 1) & 0x3def); in_buf.u8 += (in->pitch * 2); out_buf.u8 += (out->pitch * 2); } break; } } static void filter_scanline(const struct filter_data *in, struct filter_data *out) { if (out->failed == true) { failed: filter_off(in, out); return; } if (out->updated == false) { if ((screen.bpp != 32) && (screen.bpp != 24) && (screen.bpp != 16) && (screen.bpp != 15)) { DEBUG(("%u bpp depth is not supported", screen.bpp)); out->failed = true; goto failed; } out->data = malloc(sizeof(unsigned int [2])); if (out->data == NULL) { DEBUG(("allocation failure")); out->failed = true; goto failed; } if (in->width <= out->width) { unsigned int x_off = ((out->width - in->width) / 2); unsigned int y_off = ((out->height - in->height) / 2); out->buf.u8 += (x_off * screen.Bpp); out->buf.u8 += (out->pitch * y_off); out->width = in->width; } if (in->height <= out->height) out->height = in->height; ((unsigned int *)out->data)[0] = 0; ((unsigned int *)out->data)[1] = screen.bpp; out->updated = true; } filter_scanline_frame(in, out); } static void filter_interlace(const struct filter_data *in, struct filter_data *out) { if (out->failed == true) { failed: filter_off(in, out); return; } if (out->updated == false) { if ((screen.bpp != 32) && (screen.bpp != 24) && (screen.bpp != 16) && (screen.bpp != 15)) { DEBUG(("%u bpp depth is not supported", screen.bpp)); out->failed = true; goto failed; } out->data = malloc(sizeof(unsigned int [2])); if (out->data == NULL) { DEBUG(("allocation failure")); out->failed = true; goto failed; } if (in->width <= out->width) { unsigned int x_off = ((out->width - in->width) / 2); unsigned int y_off = ((out->height - in->height) / 2); out->buf.u8 += (x_off * screen.Bpp); out->buf.u8 += (out->pitch * y_off); out->width = in->width; } if (in->height <= out->height) out->height = in->height; ((unsigned int *)out->data)[0] = 0; ((unsigned int *)out->data)[1] = screen.bpp; out->updated = true; } filter_scanline_frame(in, out); ((unsigned int *)out->data)[0] ^= 1; } // Byte swap filter. static void filter_swab(const struct filter_data *in, struct filter_data *out) { bpp_t in_buf; bpp_t out_buf; unsigned int xsize; unsigned int ysize; if (out->failed == true) { failed: filter_off(in, out); return; } if (out->updated == false) { if ((screen.Bpp != 4) && (screen.Bpp != 3) && (screen.Bpp != 2)) { DEBUG(("%u Bpp depth is not supported", screen.Bpp)); out->failed = true; goto failed; } if (in->width <= out->width) { unsigned int x_off = ((out->width - in->width) / 2); unsigned int y_off = ((out->height - in->height) / 2); out->buf.u8 += (x_off * screen.Bpp); out->buf.u8 += (out->pitch * y_off); out->width = in->width; } if (in->height <= out->height) out->height = in->height; out->updated = true; } in_buf = in->buf; out_buf = out->buf; ysize = out->height; xsize = out->width; switch (screen.Bpp) { unsigned int x; unsigned int y; case 4: for (y = 0; (y < ysize); ++y) { for (x = 0; (x < xsize); ++x) { union { uint32_t u32; uint8_t u8[4]; } tmp[2]; tmp[0].u32 = in_buf.u32[x]; tmp[1].u8[0] = tmp[0].u8[3]; tmp[1].u8[1] = tmp[0].u8[2]; tmp[1].u8[2] = tmp[0].u8[1]; tmp[1].u8[3] = tmp[0].u8[0]; out_buf.u32[x] = tmp[1].u32; } in_buf.u8 += in->pitch; out_buf.u8 += out->pitch; } break; case 3: for (y = 0; (y < ysize); ++y) { for (x = 0; (x < xsize); ++x) { uint24_t tmp = { in_buf.u24[x][2], in_buf.u24[x][1], in_buf.u24[x][0] }; u24cpy(&out_buf.u24[x], &tmp); } in_buf.u8 += in->pitch; out_buf.u8 += out->pitch; } break; case 2: for (y = 0; (y < ysize); ++y) { for (x = 0; (x < xsize); ++x) out_buf.u16[x] = ((in_buf.u16[x] << 8) | (in_buf.u16[x] >> 8)); in_buf.u8 += in->pitch; out_buf.u8 += out->pitch; } break; } } #endif // WITH_CTV /** * Special characters interpreted by filter_text(). * FILTER_TEXT_BG_NONE transparent background. * FILTER_TEXT_BG_BLACK black background. * FILTER_TEXT_7X6 use 7x6 font. * FILTER_TEXT_8X13 use 8x13 font. * FILTER_TEXT_16X26 use 16x26 font. * FILTER_TEXT_CENTER center justify. * FILTER_TEXT_LEFT left justify. * FILTER_TEXT_RIGHT right justify. */ #define FILTER_TEXT_ESCAPE "\033" #define FILTER_TEXT_BG_NONE FILTER_TEXT_ESCAPE "\x01\x01" #define FILTER_TEXT_BG_BLACK FILTER_TEXT_ESCAPE "\x01\x02" #define FILTER_TEXT_7X6 FILTER_TEXT_ESCAPE "\x02\x01" #define FILTER_TEXT_8X13 FILTER_TEXT_ESCAPE "\x02\x02" #define FILTER_TEXT_16X26 FILTER_TEXT_ESCAPE "\x02\x03" #define FILTER_TEXT_CENTER FILTER_TEXT_ESCAPE "\x03\x01" #define FILTER_TEXT_LEFT FILTER_TEXT_ESCAPE "\x03\x02" #define FILTER_TEXT_RIGHT FILTER_TEXT_ESCAPE "\x03\x03" static char filter_text_str[2048]; /** * Append message to filter_text_str[]. */ static void filter_text_msg(const char *fmt, ...) { size_t off; size_t len = sizeof(filter_text_str); va_list vl; assert(filter_text_str[(len - 1)] == '\0'); off = strlen(filter_text_str); len -= off; if (len == 0) return; va_start(vl, fmt); vsnprintf(&filter_text_str[off], len, fmt, vl); va_end(vl); } /** * Text overlay filter. * @param in Input buffer data. * @param out Output buffer data. */ static void filter_text(const struct filter_data *in, struct filter_data *out) { bpp_t buf = out->buf; unsigned int buf_pitch = out->pitch; unsigned int xsize = out->width; unsigned int ysize = out->height; unsigned int bpp = screen.bpp; unsigned int Bpp = ((bpp + 1) / 8); const char *str = filter_text_str; const char *next = str; bool clear = false; bool flush = false; enum { LEFT, CENTER, RIGHT } justify = LEFT; const struct { enum font_type type; unsigned int width; unsigned int height; } font_data[] = { { FONT_TYPE_7X5, 7, (5 + 1) }, // +1 for vertical spacing. { FONT_TYPE_8X13, 8, 13 }, { FONT_TYPE_16X26, 16, 26 } }, *font = &font_data[0], *old_font = font; unsigned int line_length = 0; unsigned int line_off = 0; unsigned int line_width = 0; unsigned int line_height = font->height; // Input is unused. (void)in; assert(filter_text_str[(sizeof(filter_text_str) - 1)] == '\0'); while (1) { unsigned int len; unsigned int width; if ((*next == '\0') || (*next == '\n')) { trunc: if (flush == false) { next = str; assert(line_width <= xsize); switch (justify) { case LEFT: line_off = 0; break; case CENTER: line_off = ((xsize - line_width) / 2); break; case RIGHT: line_off = (xsize - line_width); break; } if (clear) memset(buf.u8, 0, (buf_pitch * line_height)); font = old_font; flush = true; } else if (*next == '\0') break; else { if (*next == '\n') ++next; str = next; old_font = font; line_length = 0; line_off = 0; line_width = 0; buf.u8 += (buf_pitch * line_height); ysize -= line_height; line_height = font->height; // Still enough vertical pixels for this line? if (ysize < line_height) break; flush = false; } } else if (*next == *FILTER_TEXT_ESCAPE) { const char *tmp; size_t sz; #define FILTER_TEXT_IS(f) \ (tmp = (f), sz = strlen(f), \ !strncmp(tmp, next, sz)) if (FILTER_TEXT_IS(FILTER_TEXT_BG_NONE)) clear = false; else if (FILTER_TEXT_IS(FILTER_TEXT_BG_BLACK)) clear = true; else if (FILTER_TEXT_IS(FILTER_TEXT_CENTER)) justify = CENTER; else if (FILTER_TEXT_IS(FILTER_TEXT_LEFT)) justify = LEFT; else if (FILTER_TEXT_IS(FILTER_TEXT_RIGHT)) justify = RIGHT; else if (FILTER_TEXT_IS(FILTER_TEXT_7X6)) font = &font_data[0]; else if (FILTER_TEXT_IS(FILTER_TEXT_8X13)) font = &font_data[1]; else if (FILTER_TEXT_IS(FILTER_TEXT_16X26)) font = &font_data[2]; next += sz; } else if ((line_width + font->width) <= xsize) { ++line_length; line_width += font->width; if (line_height < font->height) { line_height = font->height; // Still enough vertical pixels for this line? if (ysize < line_height) break; } ++next; } else // Truncate line. goto trunc; if (flush == false) continue; // Compute number of characters and width. len = 0; width = 0; while ((len != line_length) && (next[len] != '\0') && (next[len] != '\n') && (next[len] != *FILTER_TEXT_ESCAPE)) { width += font->width; ++len; } // Display. len = font_text((buf.u8 + // Horizontal offset. (line_off * Bpp) + // Vertical offset. ((line_height - font->height) * buf_pitch)), (xsize - line_off), line_height, Bpp, buf_pitch, next, len, ~0u, font->type); line_off += width; next += len; } } static const struct filter filter_text_def = { "text", filter_text, true, false, false }; #ifdef WITH_CTV static void set_swab() { const struct filter *f = filters_find("swab"); if (f == NULL) return; filters_pluck(f); if (dgen_swab) filters_insert(f); } static int prompt_cmd_filter_push(class md&, unsigned int ac, const char** av) { unsigned int i; if (ac < 2) return CMD_EINVAL; for (i = 1; (i != ac); ++i) { const struct filter *f = filters_find(av[i]); if (f == NULL) return CMD_EINVAL; filters_push(f); } return CMD_OK; } static char* prompt_cmpl_filter_push(class md&, unsigned int ac, const char** av, unsigned int len) { const struct filter *f; const char *prefix; unsigned int skip; unsigned int i; assert(ac != 0); if ((ac == 1) || (len == ~0u) || (av[(ac - 1)] == NULL)) { prefix = ""; len = 0; } else prefix = av[(ac - 1)]; skip = prompt.skip; retry: for (i = 0; (i != elemof(filters_available)); ++i) { f = &filters_available[i]; if (strncasecmp(prefix, f->name, len)) continue; if (skip == 0) break; --skip; } if (i == elemof(filters_available)) { if (prompt.skip != 0) { prompt.skip = 0; goto retry; } return NULL; } ++prompt.skip; return strdup(f->name); } static int prompt_cmd_filter_pop(class md&, unsigned int ac, const char**) { if (ac != 1) return CMD_EINVAL; filters_pop(); return CMD_OK; } static int prompt_cmd_filter_none(class md&, unsigned int ac, const char**) { if (ac != 1) return CMD_EINVAL; filters_empty(); return CMD_OK; } #endif // WITH_CTV static bool calibrating = false; //< True during calibration. static unsigned int calibrating_controller; ///< Controller being calibrated. static void manage_calibration(enum rc_binding_type type, intptr_t code); /** * Interactively calibrate a controller. * If n_args == 1, controller 0 will be configured. * If n_args == 2, configure controller in string args[1]. * @param n_args Number of arguments. * @param[in] args List of arguments. * @return Status code. */ static int prompt_cmd_calibrate(class md&, unsigned int n_args, const char** args) { /* check args first */ if (n_args == 1) calibrating_controller = 0; else if (n_args == 2) { calibrating_controller = (atoi(args[1]) - 1); if (calibrating_controller > 1) return CMD_EINVAL; } else return CMD_EINVAL; manage_calibration(RCB_NUM, -1); return (CMD_OK | CMD_MSG); } static int set_scaling(const char *name) { unsigned int i = filters_stack_size; assert(i <= elemof(filters_stack)); // Replace all current scalers with these. while (i != 0) { --i; if (filters_stack[i]->resize == true) filters_remove(i); } while (name += strspn(name, " \t\n"), name[0] != '\0') { const struct filter *f; int len = strcspn(name, " \t\n"); char token[64]; snprintf(token, sizeof(token), "%.*s", len, name); name += len; if (((f = filters_find(token)) == NULL) || (filters_stack_size == elemof(filters_stack))) return -1; filters_push(f); } return 0; } /** * Display splash screen. */ static void mdscr_splash() { unsigned int x; unsigned int y; bpp_t src; unsigned int src_pitch = (dgen_splash_data.width * dgen_splash_data.bytes_per_pixel); unsigned int sw = dgen_splash_data.width; unsigned int sh = dgen_splash_data.height; bpp_t dst; unsigned int dst_pitch = mdscr.pitch; unsigned int dw = video.width; unsigned int dh = video.height; if ((dgen_splash_data.bytes_per_pixel != 3) || (sw != dw)) return; src.u8 = (uint8_t *)dgen_splash_data.pixel_data; dst.u8 = ((uint8_t *)mdscr.data + (dst_pitch * 8) + 16); // Center it. if (sh < dh) { unsigned int off = ((dh - sh) / 2); memset(dst.u8, 0x00, (dst_pitch * off)); memset(&dst.u8[(dst_pitch * (off + sh))], 0x00, (dst_pitch * (dh - (off + sh)))); dst.u8 += (dst_pitch * off); } switch (mdscr.bpp) { case 32: for (y = 0; ((y != dh) && (y != sh)); ++y) { for (x = 0; ((x != dw) && (x != sw)); ++x) { dst.u32[x] = ((src.u24[x][0] << 16) | (src.u24[x][1] << 8) | (src.u24[x][2] << 0)); } src.u8 += src_pitch; dst.u8 += dst_pitch; } break; case 24: for (y = 0; ((y != dh) && (y != sh)); ++y) { for (x = 0; ((x != dw) && (x != sw)); ++x) { dst.u24[x][0] = src.u24[x][2]; dst.u24[x][1] = src.u24[x][1]; dst.u24[x][2] = src.u24[x][0]; } src.u8 += src_pitch; dst.u8 += dst_pitch; } break; case 16: for (y = 0; ((y != dh) && (y != sh)); ++y) { for (x = 0; ((x != dw) && (x != sw)); ++x) { dst.u16[x] = (((src.u24[x][0] & 0xf8) << 8) | ((src.u24[x][1] & 0xfc) << 3) | ((src.u24[x][2] & 0xf8) >> 3)); } src.u8 += src_pitch; dst.u8 += dst_pitch; } break; case 15: for (y = 0; ((y != dh) && (y != sh)); ++y) { for (x = 0; ((x != dw) && (x != sw)); ++x) { dst.u16[x] = (((src.u24[x][0] & 0xf8) << 7) | ((src.u24[x][1] & 0xf8) << 2) | ((src.u24[x][2] & 0xf8) >> 3)); } src.u8 += src_pitch; dst.u8 += dst_pitch; } break; case 8: break; } } /** * Initialize screen. * * @param width Width of display. * @param height Height of display. * @return 0 on success, -1 if screen could not be initialized with current * options but remains in its previous state, -2 if screen is unusable. */ static int screen_init(unsigned int width, unsigned int height) { static bool once = true; uint32_t flags = (SDL_RESIZABLE | SDL_ANYFORMAT | SDL_HWPALETTE | SDL_HWSURFACE); struct screen scrtmp; const struct dgen_font *font; #ifdef WITH_THREADS screen_update_thread_stop(); #endif DEBUG(("want width=%u height=%u", width, height)); stopped = 1; // Copy current screen data. memcpy(&scrtmp, &screen, sizeof(scrtmp)); if (once) { unsigned int info_height = dgen_font[FONT_TYPE_8X13].h; // Force defaults once. scrtmp.window_width = 0; scrtmp.window_height = 0; scrtmp.width = (video.width * 2); scrtmp.height = ((video.height * 2) + info_height); scrtmp.x_scale = (scrtmp.width / video.width); scrtmp.y_scale = (scrtmp.height / video.height); scrtmp.bpp = 0; scrtmp.Bpp = 0; scrtmp.info_height = info_height; scrtmp.buf.u8 = 0; scrtmp.pitch = 0; scrtmp.surface = 0; scrtmp.want_fullscreen = 0; scrtmp.is_fullscreen = 0; #ifdef WITH_OPENGL scrtmp.want_opengl = 0; scrtmp.is_opengl = 0; #endif #ifdef WITH_THREADS scrtmp.want_thread = 0; scrtmp.is_thread = 0; scrtmp.thread = 0; scrtmp.lock = 0; scrtmp.cond = 0; #endif memset(scrtmp.color, 0, sizeof(scrtmp.color)); once = false; } // Use configuration data. if (width != 0) scrtmp.width = width; if (dgen_width >= 1) scrtmp.width = dgen_width; if (height != 0) scrtmp.height = height; if (dgen_height >= 1) scrtmp.height = dgen_height; if (dgen_depth >= 0) { scrtmp.bpp = dgen_depth; scrtmp.Bpp = 0; } // scrtmp.x_scale, scrtmp.y_scale and scrtmp.info_height cannot be // determined yet. scrtmp.want_fullscreen = !!dgen_fullscreen; #ifdef WITH_OPENGL opengl_failed: scrtmp.want_opengl = !!dgen_opengl; #endif #ifdef WITH_THREADS scrtmp.want_thread = !!dgen_screen_thread; #endif // Configure SDL_SetVideoMode(). if (scrtmp.want_fullscreen) flags |= SDL_FULLSCREEN; #ifdef WITH_OPENGL if (scrtmp.want_opengl) { SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, !!dgen_doublebuffer); flags |= SDL_OPENGL; } else #endif flags |= ((dgen_doublebuffer ? SDL_DOUBLEBUF : 0) | SDL_ASYNCBLIT); if (scrtmp.want_fullscreen) { SDL_Rect **modes; // Check if we're going to be bound to a particular resolution. modes = SDL_ListModes(NULL, (flags | SDL_FULLSCREEN)); if ((modes != NULL) && (modes != (SDL_Rect **)-1)) { unsigned int i; struct { unsigned int i; unsigned int w; unsigned int h; } best = { 0, (unsigned int)-1, (unsigned int)-1 }; // Find the best resolution available. for (i = 0; (modes[i] != NULL); ++i) { unsigned int w, h; DEBUG(("checking mode %dx%d", modes[i]->w, modes[i]->h)); if ((modes[i]->w < scrtmp.width) || (modes[i]->h < scrtmp.height)) continue; w = (modes[i]->w - scrtmp.width); h = (modes[i]->h - scrtmp.height); if ((w <= best.w) && (h <= best.h)) { best.i = i; best.w = w; best.h = h; } } if ((best.w == (unsigned int)-1) || (best.h == (unsigned int)-1)) DEBUG(("no mode looks good")); else { scrtmp.width = modes[best.i]->w; scrtmp.height = modes[best.i]->h; DEBUG(("mode %ux%u looks okay", scrtmp.width, scrtmp.height)); } } DEBUG(("adjusted fullscreen resolution to %ux%u", scrtmp.width, scrtmp.height)); } // Set video mode. DEBUG(("SDL_SetVideoMode(%u, %u, %d, 0x%08x)", scrtmp.width, scrtmp.height, scrtmp.bpp, flags)); scrtmp.surface = SDL_SetVideoMode(scrtmp.width, scrtmp.height, scrtmp.bpp, flags); if (scrtmp.surface == NULL) { #ifdef WITH_OPENGL // Try again without OpenGL. if (flags & SDL_OPENGL) { assert(scrtmp.want_opengl); DEBUG(("OpenGL initialization failed, retrying" " without it.")); dgen_opengl = 0; flags &= ~SDL_OPENGL; goto opengl_failed; } #endif return -1; } DEBUG(("SDL_SetVideoMode succeeded")); // Update with current values. scrtmp.window_width = scrtmp.surface->w; scrtmp.window_height = scrtmp.surface->h; scrtmp.width = scrtmp.window_width; scrtmp.height = scrtmp.window_height; // By default, using 5% of the vertical resolution for info bar ought // to be good enough for anybody. Pick something close. if (dgen_info_height < 0) scrtmp.info_height = ((scrtmp.height * 5) / 100); else scrtmp.info_height = dgen_info_height; if (scrtmp.info_height > scrtmp.height) scrtmp.info_height = scrtmp.height; font = font_select(scrtmp.width, scrtmp.info_height, FONT_TYPE_AUTO); if (font == NULL) scrtmp.info_height = 0; else scrtmp.info_height = font->h; assert(scrtmp.info_height <= scrtmp.height); // Do not forget. // Determine default X and Y scale values from what remains. if (dgen_x_scale >= 0) scrtmp.x_scale = dgen_x_scale; else scrtmp.x_scale = (scrtmp.width / video.width); if (dgen_y_scale >= 0) scrtmp.y_scale = dgen_y_scale; else scrtmp.y_scale = ((scrtmp.height - scrtmp.info_height) / video.height); if (dgen_aspect) { if (scrtmp.x_scale >= scrtmp.y_scale) scrtmp.x_scale = scrtmp.y_scale; else scrtmp.y_scale = scrtmp.x_scale; } // Fix bpp. assert(scrtmp.surface->format != NULL); scrtmp.bpp = scrtmp.surface->format->BitsPerPixel; // 15 bpp has be forced if it was required. SDL does not return the // right value. if ((dgen_depth == 15) && (scrtmp.bpp == 16)) scrtmp.bpp = 15; scrtmp.Bpp = scrtmp.surface->format->BytesPerPixel; scrtmp.buf.u8 = (uint8_t *)scrtmp.surface->pixels; scrtmp.pitch = scrtmp.surface->pitch; scrtmp.is_fullscreen = scrtmp.want_fullscreen; DEBUG(("video configuration: x_scale=%u y_scale=%u", scrtmp.x_scale, scrtmp.y_scale)); DEBUG(("screen configuration: width=%u height=%u bpp=%u Bpp=%u" " info_height=%u" " buf.u8=%p pitch=%u surface=%p want_fullscreen=%u" " is_fullscreen=%u", scrtmp.width, scrtmp.height, scrtmp.bpp, scrtmp.Bpp, scrtmp.info_height, (void *)scrtmp.buf.u8, scrtmp.pitch, (void *)scrtmp.surface, scrtmp.want_fullscreen, scrtmp.is_fullscreen)); #ifdef WITH_OPENGL if (scrtmp.want_opengl) { if (init_texture(&scrtmp)) { DEBUG(("OpenGL initialization failed, retrying" " without it.")); dgen_opengl = 0; flags &= ~SDL_OPENGL; goto opengl_failed; } // Update using texture info. scrtmp.Bpp = (2 << scrtmp.texture.u32); scrtmp.bpp = (scrtmp.Bpp * 8); scrtmp.buf.u32 = scrtmp.texture.buf.u32; scrtmp.width = scrtmp.texture.vis_width; scrtmp.height = scrtmp.texture.vis_height; scrtmp.pitch = (scrtmp.texture.vis_width << (1 << scrtmp.texture.u32)); } scrtmp.is_opengl = scrtmp.want_opengl; DEBUG(("OpenGL screen configuration: is_opengl=%u buf.u32=%p pitch=%u", scrtmp.is_opengl, (void *)scrtmp.buf.u32, scrtmp.pitch)); #endif // Screen is now initialized, update data. screen = scrtmp; #ifdef WITH_OPENGL if (!screen.is_opengl) { // Free OpenGL resources. DEBUG(("releasing OpenGL resources")); release_texture(screen.texture); } #endif // Set up the Mega Drive screen. // Could not be done earlier because bpp was unknown. if ((mdscr.data == NULL) || ((unsigned int)mdscr.bpp != screen.bpp) || ((unsigned int)mdscr.w != (video.width + 16)) || ((unsigned int)mdscr.h != (video.height + 16))) { mdscr.w = (video.width + 16); mdscr.h = (video.height + 16); mdscr.pitch = (mdscr.w * screen.Bpp); mdscr.bpp = screen.bpp; free(mdscr.data); mdscr.data = (uint8_t *)calloc(mdscr.h, mdscr.pitch); if (mdscr.data == NULL) { // Cannot recover. Clean up and bail out. memset(&mdscr, 0, sizeof(mdscr)); return -2; } mdscr_splash(); } DEBUG(("md screen configuration: w=%d h=%d bpp=%d pitch=%d data=%p", mdscr.w, mdscr.h, mdscr.bpp, mdscr.pitch, (void *)mdscr.data)); // If we're in 8 bit mode, set color 0xff to white for the text, // and make a palette buffer. if (screen.bpp == 8) { SDL_Color color = { 0xff, 0xff, 0xff, 0x00 }; SDL_SetColors(screen.surface, &color, 0xff, 1); memset(video.palette, 0x00, sizeof(video.palette)); mdpal = video.palette; } else mdpal = NULL; #ifdef WITH_THREADS if (screen.want_thread) screen_update_thread_start(); #endif // Rehash filters. filters_stack_update(); // Update screen. pd_graphics_update(true); return 0; } /** * Set fullscreen mode. * @param toggle Nonzero to enable fullscreen, otherwise disable it. * @return 0 on success. */ static int set_fullscreen(int toggle) { unsigned int w; unsigned int h; if (((!toggle) && (!screen.is_fullscreen)) || ((toggle) && (screen.is_fullscreen))) { // Already in the desired mode. DEBUG(("already %s fullscreen mode, ret=-1", (toggle ? "in" : "not in"))); return -1; } #ifdef HAVE_SDL_WM_TOGGLEFULLSCREEN // Try this first. DEBUG(("trying SDL_WM_ToggleFullScreen(%p)", (void *)screen.surface)); if (SDL_WM_ToggleFullScreen(screen.surface)) return 0; DEBUG(("falling back to screen_init()")); #endif dgen_fullscreen = toggle; if (screen.surface != NULL) { // Try to keep the current mode. w = screen.surface->w; h = screen.surface->h; } else if ((dgen_width > 0) && (dgen_height > 0)) { // Use configured mode. w = dgen_width; h = dgen_height; } else { // Try to make a guess. w = (video.width * screen.x_scale); h = (video.height * screen.y_scale); } DEBUG(("reinitializing screen with want_fullscreen=%u," " screen_init(%u, %u)", screen.want_fullscreen, w, h)); return screen_init(w, h); } /** * Initialize SDL, and the graphics. * @param want_sound Nonzero if we want sound. * @param want_pal Nonzero for PAL mode. * @param hz Requested frame rate (between 0 and 1000). * @return Nonzero if successful. */ int pd_graphics_init(int want_sound, int want_pal, int hz) { SDL_Event event; prompt_init(&prompt.status); if ((hz <= 0) || (hz > 1000)) { // You may as well disable bool_frameskip. fprintf(stderr, "sdl: invalid frame rate (%d)\n", hz); return 0; } video.hz = hz; if (want_pal) { // PAL video.is_pal = 1; video.height = 240; } else { // NTSC video.is_pal = 0; video.height = 224; } #if !defined __MINGW32__ && !defined _KOLIBRI // [fbcon workaround] // Disable SDL_FBACCEL (if unset) before calling SDL_Init() in case // fbcon is to be used. Prevents SDL_FillRect() and SDL_SetVideoMode() // from hanging when hardware acceleration is available. setenv("SDL_FBACCEL", "0", 0); // [fbcon workaround] // A mouse is never required. setenv("SDL_NOMOUSE", "1", 0); #endif if (SDL_Init(SDL_INIT_VIDEO | (want_sound ? SDL_INIT_AUDIO : 0))) { fprintf(stderr, "sdl: can't init SDL: %s\n", SDL_GetError()); return 0; } #ifndef __MINGW32__ { char buf[32]; // [fbcon workaround] // Double buffering usually makes screen blink during refresh. if ((SDL_VideoDriverName(buf, sizeof(buf))) && (!strcmp(buf, "fbcon"))) dgen_doublebuffer = 0; } #endif // Required for text input. SDL_EnableUNICODE(1); // Set the titlebar. SDL_WM_SetCaption("DGen/SDL " VER, "DGen/SDL " VER); // Hide the cursor. SDL_ShowCursor(0); // Initialize screen. if (screen_init(0, 0)) goto fail; // Initialize scaling. set_scaling(scaling_names[dgen_scaling % NUM_SCALING]); DEBUG(("using scaling filter \"%s\"", scaling_names[dgen_scaling % NUM_SCALING])); DEBUG(("screen initialized")); #if !defined __MINGW32__ && !defined _KOLIBRI // We don't need setuid privileges anymore if (getuid() != geteuid()) setuid(getuid()); DEBUG(("setuid privileges dropped")); #endif #ifdef WITH_CTV filters_pluck_ctv(); { const struct filter *f; f = filters_find(ctv_names[dgen_craptv % NUM_CTV]); if ((f != NULL) && (f->func != filter_off)) filters_insert(f); } #endif // WITH_CTV DEBUG(("ret=1")); fprintf(stderr, "video: %dx%d, %u bpp (%u Bpp), %uHz\n", screen.surface->w, screen.surface->h, screen.bpp, screen.Bpp, video.hz); #ifdef WITH_OPENGL if (screen.is_opengl) { DEBUG(("GL_VENDOR=\"%s\" GL_RENDERER=\"%s\"" " GL_VERSION=\"%s\"", glGetString(GL_VENDOR), glGetString(GL_RENDERER), glGetString(GL_VERSION))); fprintf(stderr, "video: OpenGL texture %ux%ux%u (%ux%u)\n", screen.texture.width, screen.texture.height, (2 << screen.texture.u32), screen.texture.vis_width, screen.texture.vis_height); } #endif while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_VIDEORESIZE: if (screen_init(event.resize.w, event.resize.h)) goto fail; break; } } return 1; fail: fprintf(stderr, "sdl: can't initialize graphics.\n"); return 0; } /** * Reinitialize graphics. * @param want_pal Nonzero for PAL mode. * @param hz Requested frame rate (between 0 and 1000). * @return Nonzero if successful. */ int pd_graphics_reinit(int, int want_pal, int hz) { if ((hz <= 0) || (hz > 1000)) { // You may as well disable bool_frameskip. fprintf(stderr, "sdl: invalid frame rate (%d)\n", hz); return 0; } video.hz = hz; if (want_pal) { // PAL video.is_pal = 1; video.height = 240; } else { // NTSC video.is_pal = 0; video.height = 224; } // Reinitialize screen. if (screen_init(screen.window_width, screen.window_height)) goto fail; DEBUG(("screen reinitialized")); return 1; fail: fprintf(stderr, "sdl: can't reinitialize graphics.\n"); return 0; } /** * Update palette. */ void pd_graphics_palette_update() { unsigned int i; for (i = 0; (i < 64); ++i) { screen.color[i].r = mdpal[(i << 2)]; screen.color[i].g = mdpal[((i << 2) + 1)]; screen.color[i].b = mdpal[((i << 2) + 2)]; } #ifdef WITH_OPENGL if (!screen.is_opengl) #endif SDL_SetColors(screen.surface, screen.color, 0, 64); } /** * Display screen. * @param update False if screen buffer is garbage and must be updated first. */ void pd_graphics_update(bool update) { static unsigned long fps_since = 0; static unsigned long frames_old = 0; static unsigned long frames = 0; unsigned long usecs = pd_usecs(); const struct filter *f; struct filter_data *fd; size_t i; // Check whether the message must be processed. if ((events == STARTED) && ((info.displayed) || (info.length)) && ((usecs - info.since) >= MESSAGE_LIFE)) pd_message_process(); else if (dgen_fps) { unsigned long tmp = ((usecs - fps_since) & 0x3fffff); ++frames; if (tmp >= 1000000) { unsigned long fps; fps_since = usecs; if (frames_old > frames) fps = (frames_old - frames); else fps = (frames - frames_old); frames_old = frames; if (!info.displayed) { char buf[16]; snprintf(buf, sizeof(buf), "%lu FPS", fps); pd_message_write(buf, strlen(buf), ~0u); } } } if (update == false) mdscr_splash(); // Process output through filters. for (i = 0; (i != elemof(filters_stack)); ++i) { f = filters_stack[i]; fd = &filters_stack_data[i]; if ((filters_stack_size == 0) || (i == (filters_stack_size - 1))) break; f->func(fd, (fd + 1)); } // Lock screen. if (screen_lock()) return; // Generate screen output with the last filter. f->func(fd, (fd + 1)); // Unlock screen. screen_unlock(); // Update the screen. screen_update(); } /** * Callback for sound. * @param stream Sound destination buffer. * @param len Length of destination buffer. */ static void snd_callback(void *, Uint8 *stream, int len) { size_t wrote; // Slurp off the play buffer wrote = cbuf_read(stream, &sound.cbuf, len); if (wrote == (size_t)len) return; // Not enough data, fill remaining space with silence. memset(&stream[wrote], 0, ((size_t)len - wrote)); } /** * Initialize the sound. * @param freq Sound samples rate. * @param[in,out] samples Minimum buffer size in samples. * @return Nonzero on success. */ int pd_sound_init(long &freq, unsigned int &samples) { SDL_AudioSpec wanted; SDL_AudioSpec spec; // Clean up first. pd_sound_deinit(); // Set the desired format wanted.freq = freq; #ifdef WORDS_BIGENDIAN wanted.format = AUDIO_S16MSB; #else wanted.format = AUDIO_S16LSB; #endif wanted.channels = 2; wanted.samples = dgen_soundsamples; wanted.callback = snd_callback; wanted.userdata = NULL; if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { fprintf(stderr, "sdl: unable to initialize audio\n"); return 0; } // Open audio, and get the real spec if (SDL_OpenAudio(&wanted, &spec) < 0) { fprintf(stderr, "sdl: couldn't open audio: %s\n", SDL_GetError()); return 0; } // Check everything if (spec.channels != 2) { fprintf(stderr, "sdl: couldn't get stereo audio format.\n"); goto snd_error; } if (spec.format != wanted.format) { fprintf(stderr, "sdl: unable to get 16-bit audio.\n"); goto snd_error; } // Set things as they really are sound.rate = freq = spec.freq; sndi.len = (spec.freq / video.hz); sound.samples = spec.samples; samples += sound.samples; // Calculate buffer size (sample size = (channels * (bits / 8))). sound.cbuf.size = (samples * (2 * (16 / 8))); sound.cbuf.i = 0; sound.cbuf.s = 0; fprintf(stderr, "sound: %uHz, %d samples, buffer: %u bytes\n", sound.rate, spec.samples, (unsigned int)sound.cbuf.size); // Allocate zero-filled play buffer. sndi.lr = (int16_t *)calloc(2, (sndi.len * sizeof(sndi.lr[0]))); sound.cbuf.data.i16 = (int16_t *)calloc(1, sound.cbuf.size); if ((sndi.lr == NULL) || (sound.cbuf.data.i16 == NULL)) { fprintf(stderr, "sdl: couldn't allocate sound buffers.\n"); goto snd_error; } // Start sound output. SDL_PauseAudio(0); // It's all good! return 1; snd_error: // Oops! Something bad happened, cleanup. SDL_CloseAudio(); free((void *)sndi.lr); sndi.lr = NULL; sndi.len = 0; free((void *)sound.cbuf.data.i16); sound.cbuf.data.i16 = NULL; memset(&sound, 0, sizeof(sound)); return 0; } /** * Deinitialize sound subsystem. */ void pd_sound_deinit() { if (sound.cbuf.data.i16 != NULL) { SDL_PauseAudio(1); SDL_CloseAudio(); free((void *)sound.cbuf.data.i16); } memset(&sound, 0, sizeof(sound)); free((void*)sndi.lr); sndi.lr = NULL; } /** * Return samples read/write indices in the buffer. */ unsigned int pd_sound_rp() { unsigned int ret; if (!sound.cbuf.size) return 0; SDL_LockAudio(); ret = sound.cbuf.i; SDL_UnlockAudio(); return (ret >> 2); } unsigned int pd_sound_wp() { unsigned int ret; if (!sound.cbuf.size) return 0; SDL_LockAudio(); ret = ((sound.cbuf.i + sound.cbuf.s) % sound.cbuf.size); SDL_UnlockAudio(); return (ret >> 2); } /** * Write contents of sndi to sound.cbuf. */ void pd_sound_write() { if (!sound.cbuf.size) return; SDL_LockAudio(); cbuf_write(&sound.cbuf, (uint8_t *)sndi.lr, (sndi.len * 4)); SDL_UnlockAudio(); } /** * Tells whether DGen stopped intentionally so emulation can resume without * skipping frames. */ int pd_stopped() { int ret = stopped; stopped = 0; return ret; } /** * Keyboard input. */ typedef struct { char *buf; size_t pos; size_t size; } kb_input_t; /** * Keyboard input results. */ enum kb_input { KB_INPUT_ABORTED, KB_INPUT_ENTERED, KB_INPUT_CONSUMED, KB_INPUT_IGNORED }; /** * Manage text input with some rudimentary history. * @param input Input buffer. * @param ksym Keyboard symbol. * @param ksym_uni Unicode translation for keyboard symbol. * @return Input result. */ static enum kb_input kb_input(kb_input_t *input, uint32_t ksym, uint16_t ksym_uni) { #define HISTORY_LEN 32 static char history[HISTORY_LEN][64]; static int history_pos = -1; static int history_len = 0; char c; if (ksym & KEYSYM_MOD_CTRL) return KB_INPUT_IGNORED; if (isprint((c = ksym_uni))) { if (input->pos >= (input->size - 1)) return KB_INPUT_CONSUMED; if (input->buf[input->pos] == '\0') input->buf[(input->pos + 1)] = '\0'; input->buf[input->pos] = c; ++input->pos; return KB_INPUT_CONSUMED; } else if (ksym == SDLK_DELETE) { size_t tail; if (input->buf[input->pos] == '\0') return KB_INPUT_CONSUMED; tail = ((input->size - input->pos) + 1); memmove(&input->buf[input->pos], &input->buf[(input->pos + 1)], tail); return KB_INPUT_CONSUMED; } else if (ksym == SDLK_BACKSPACE) { size_t tail; if (input->pos == 0) return KB_INPUT_CONSUMED; --input->pos; tail = ((input->size - input->pos) + 1); memmove(&input->buf[input->pos], &input->buf[(input->pos + 1)], tail); return KB_INPUT_CONSUMED; } else if (ksym == SDLK_LEFT) { if (input->pos != 0) --input->pos; return KB_INPUT_CONSUMED; } else if (ksym == SDLK_RIGHT) { if (input->buf[input->pos] != '\0') ++input->pos; return KB_INPUT_CONSUMED; } else if ((ksym == SDLK_RETURN) || (ksym == SDLK_KP_ENTER)) { history_pos = -1; if (input->pos == 0) return KB_INPUT_ABORTED; if (history_len < HISTORY_LEN) ++history_len; memmove(&history[1], &history[0], ((history_len - 1) * sizeof(history[0]))); strncpy(history[0], input->buf, sizeof(history[0])); return KB_INPUT_ENTERED; } else if (ksym == SDLK_ESCAPE) { history_pos = 0; return KB_INPUT_ABORTED; } else if (ksym == SDLK_UP) { if (input->size == 0) return KB_INPUT_CONSUMED; if (history_pos < (history_len - 1)) ++history_pos; strncpy(input->buf, history[history_pos], input->size); input->buf[(input->size - 1)] = '\0'; input->pos = strlen(input->buf); return KB_INPUT_CONSUMED; } else if (ksym == SDLK_DOWN) { if ((input->size == 0) || (history_pos < 0)) return KB_INPUT_CONSUMED; if (history_pos > 0) --history_pos; strncpy(input->buf, history[history_pos], input->size); input->buf[(input->size - 1)] = '\0'; input->pos = strlen(input->buf); return KB_INPUT_CONSUMED; } return KB_INPUT_IGNORED; } /** * Write a message to the status bar while displaying a cursor and without * buffering. */ static void pd_message_cursor(unsigned int mark, const char *msg, ...) { va_list vl; char buf[1024]; size_t len; size_t disp_len; va_start(vl, msg); len = (size_t)vsnprintf(buf, sizeof(buf), msg, vl); va_end(vl); buf[(sizeof(buf) - 1)] = '\0'; disp_len = font_text_max_len(screen.width, screen.info_height, FONT_TYPE_AUTO); if (mark > len) { if (len <= disp_len) pd_message_display(buf, len, ~0u, true); else pd_message_display(&(buf[(len - disp_len)]), disp_len, ~0u, true); return; } if (len <= disp_len) pd_message_display(buf, len, mark, true); else if (len == mark) pd_message_display(&buf[((len - disp_len) + 1)], disp_len, (disp_len - 1), true); else if ((len - mark) < disp_len) pd_message_display(&buf[(len - disp_len)], disp_len, (mark - (len - disp_len)), true); else if (mark != ~0u) pd_message_display(&buf[mark], disp_len, 0, true); } /** * Rehash rc vars that require special handling (see "SH" in rc.cpp). */ static int prompt_rehash_rc_field(const struct rc_field *rc, md& megad) { bool fail = false; bool init_video = false; bool init_sound = false; bool init_joystick = false; if (rc->variable == &dgen_craptv) { #ifdef WITH_CTV filters_pluck_ctv(); filters_insert(filters_find(ctv_names[dgen_craptv % NUM_CTV])); #else fail = true; #endif } else if (rc->variable == &dgen_scaling) { if (set_scaling(scaling_names[dgen_scaling]) == 0) fail = true; } else if (rc->variable == &dgen_emu_z80) { megad.z80_state_dump(); // Z80: 0 = none, 1 = CZ80, 2 = MZ80, 3 = DRZ80 switch (dgen_emu_z80) { #ifdef WITH_MZ80 case 1: megad.z80_core = md::Z80_CORE_MZ80; break; #endif #ifdef WITH_CZ80 case 2: megad.z80_core = md::Z80_CORE_CZ80; break; #endif #ifdef WITH_DRZ80 case 3: megad.z80_core = md::Z80_CORE_DRZ80; break; #endif default: megad.z80_core = md::Z80_CORE_NONE; break; } megad.z80_state_restore(); } else if (rc->variable == &dgen_emu_m68k) { megad.m68k_state_dump(); // M68K: 0 = none, 1 = StarScream, 2 = Musashi, 3 = Cyclone switch (dgen_emu_m68k) { #ifdef WITH_STAR case 1: megad.cpu_emu = md::CPU_EMU_STAR; break; #endif #ifdef WITH_MUSA case 2: megad.cpu_emu = md::CPU_EMU_MUSA; break; #endif #ifdef WITH_CYCLONE case 3: megad.cpu_emu = md::CPU_EMU_CYCLONE; break; #endif default: megad.cpu_emu = md::CPU_EMU_NONE; break; } megad.m68k_state_restore(); } else if ((rc->variable == &dgen_sound) || (rc->variable == &dgen_soundrate) || (rc->variable == &dgen_soundsegs) || (rc->variable == &dgen_soundsamples) || (rc->variable == &dgen_mjazz)) init_sound = true; else if (rc->variable == &dgen_fullscreen) { if (screen.want_fullscreen != (!!dgen_fullscreen)) { init_video = true; } } else if ((rc->variable == &dgen_info_height) || (rc->variable == &dgen_width) || (rc->variable == &dgen_height) || (rc->variable == &dgen_x_scale) || (rc->variable == &dgen_y_scale) || (rc->variable == &dgen_depth) || (rc->variable == &dgen_doublebuffer) || (rc->variable == &dgen_screen_thread)) init_video = true; else if (rc->variable == &dgen_swab) { #ifdef WITH_CTV set_swab(); #else fail = true; #endif } else if ((rc->variable == &dgen_scale) || (rc->variable == &dgen_aspect)) { dgen_x_scale = dgen_scale; dgen_y_scale = dgen_scale; init_video = true; } else if ((rc->variable == &dgen_opengl) || (rc->variable == &dgen_opengl_stretch) || (rc->variable == &dgen_opengl_linear) || (rc->variable == &dgen_opengl_32bit) || (rc->variable == &dgen_opengl_square)) { #ifdef WITH_OPENGL init_video = true; #else (void)0; #endif } else if (rc->variable == &dgen_joystick) init_joystick = true; else if (rc->variable == &dgen_hz) { // See md::md(). if (dgen_hz <= 0) dgen_hz = 1; else if (dgen_hz > 1000) dgen_hz = 1000; if (((unsigned int)dgen_hz != video.hz) || ((unsigned int)dgen_hz != megad.vhz)) { video.hz = dgen_hz; init_video = true; init_sound = true; } } else if (rc->variable == &dgen_pal) { // See md::md(). if ((dgen_pal) && ((video.is_pal == false) || (megad.pal == 0) || (video.height != PAL_VBLANK))) { megad.pal = 1; megad.init_pal(); video.is_pal = true; video.height = PAL_VBLANK; init_video = true; } else if ((!dgen_pal) && ((video.is_pal == true) || (megad.pal == 1) || (video.height != NTSC_VBLANK))) { megad.pal = 0; megad.init_pal(); video.is_pal = false; video.height = NTSC_VBLANK; init_video = true; } } else if (rc->variable == &dgen_region) { uint8_t c; int hz; int pal; int vblank; if (dgen_region) c = dgen_region; else c = megad.region_guess(); md::region_info(c, &pal, &hz, &vblank, 0, 0); if ((hz != dgen_hz) || (pal != dgen_pal) || (c != megad.region)) { megad.region = c; dgen_hz = hz; dgen_pal = pal; megad.pal = pal; megad.init_pal(); video.is_pal = pal; video.height = vblank; video.hz = hz; init_video = true; init_sound = true; fprintf(stderr, "sdl: reconfiguring for region \"%c\": " "%dHz (%s)\n", megad.region, hz, (pal ? "PAL" : "NTSC")); } } else if (rc->variable == (intptr_t *)((void *)&dgen_rom_path)) set_rom_path(dgen_rom_path.val); if (init_video) { // This is essentially what pd_graphics_init() does. memset(megad.vdp.dirt, 0xff, 0x35); switch (screen_init(screen.window_width, screen.window_height)) { case 0: break; case -1: goto video_warn; default: goto video_fail; } } if (init_sound) { if (video.hz == 0) fail = true; else if (dgen_sound == 0) pd_sound_deinit(); else { uint8_t ym2612_buf[512]; uint8_t sn76496_buf[16]; unsigned int samples; long rate = dgen_soundrate; pd_sound_deinit(); samples = (dgen_soundsegs * (rate / video.hz)); if (!pd_sound_init(rate, samples)) fail = true; YM2612_dump(0, ym2612_buf); SN76496_dump(0, sn76496_buf); megad.init_sound(); SN76496_restore(0, sn76496_buf); YM2612_restore(0, ym2612_buf); } } if (init_joystick) { #ifdef WITH_JOYSTICK megad.deinit_joysticks(); if (dgen_joystick) megad.init_joysticks(); #else fail = true; #endif } if (fail) { pd_message("Failed to rehash value."); return (PROMPT_RET_EXIT | PROMPT_RET_MSG); } return PROMPT_RET_CONT; video_warn: pd_message("Failed to reinitialize video."); return (PROMPT_RET_EXIT | PROMPT_RET_MSG); video_fail: fprintf(stderr, "sdl: fatal error while trying to change screen" " resolution.\n"); return (PROMPT_RET_ERROR | PROMPT_RET_MSG); } static void prompt_show_rc_field(const struct rc_field *rc) { size_t i; intptr_t val = *rc->variable; if ((rc->parser == rc_number) || (rc->parser == rc_soundrate)) pd_message("%s is %ld", rc->fieldname, val); else if (rc->parser == rc_keysym) { char *ks = dump_keysym(val); if ((ks == NULL) || (ks[0] == '\0')) pd_message("%s isn't bound", rc->fieldname); else pd_message("%s is bound to \"%s\"", rc->fieldname, ks); free(ks); } else if (rc->parser == rc_boolean) pd_message("%s is %s", rc->fieldname, ((val) ? "true" : "false")); else if (rc->parser == rc_joypad) { char *js = dump_joypad(val); if ((js == NULL) || (js[0] == '\0')) pd_message("%s isn't bound", rc->fieldname); else pd_message("%s is bound to \"%s\"", rc->fieldname, js); free(js); } else if (rc->parser == rc_mouse) { char *mo = dump_mouse(val); if ((mo == NULL) || (mo[0] == '\0')) pd_message("%s isn't bound", rc->fieldname); else pd_message("%s is bound to \"%s\"", rc->fieldname, mo); free(mo); } else if (rc->parser == rc_ctv) { i = val; if (i >= NUM_CTV) pd_message("%s is undefined", rc->fieldname); else pd_message("%s is \"%s\"", rc->fieldname, ctv_names[i]); } else if (rc->parser == rc_scaling) { i = val; if (i >= NUM_SCALING) pd_message("%s is undefined", rc->fieldname); else pd_message("%s is \"%s\"", rc->fieldname, scaling_names[i]); } else if (rc->parser == rc_emu_z80) { for (i = 0; (emu_z80_names[i] != NULL); ++i) if (i == (size_t)val) break; if (emu_z80_names[i] == NULL) pd_message("%s is undefined", rc->fieldname); else pd_message("%s is \"%s\"", rc->fieldname, emu_z80_names[i]); } else if (rc->parser == rc_emu_m68k) { for (i = 0; (emu_m68k_names[i] != NULL); ++i) if (i == (size_t)val) break; if (emu_m68k_names[i] == NULL) pd_message("%s is undefined", rc->fieldname); else pd_message("%s is \"%s\"", rc->fieldname, emu_m68k_names[i]); } else if (rc->parser == rc_region) { const char *s; if (val == 'U') s = "America (NTSC)"; else if (val == 'E') s = "Europe (PAL)"; else if (val == 'J') s = "Japan (NTSC)"; else if (val == 'X') s = "Japan (PAL)"; else s = "Auto"; pd_message("%s is \"%c\" (%s)", rc->fieldname, (val ? (char)val : (char)' '), s); } else if ((rc->parser == rc_string) || (rc->parser == rc_rom_path)) { struct rc_str *rs = (struct rc_str *)rc->variable; char *s; if (rs->val == NULL) pd_message("%s has no value", rc->fieldname); else if ((s = backslashify((const uint8_t *)rs->val, strlen(rs->val), 0, NULL)) != NULL) { pd_message("%s is \"%s\"", rc->fieldname, s); free(s); } else pd_message("%s can't be displayed", rc->fieldname); } else if (rc->parser == rc_bind) { char *f = backslashify((uint8_t *)rc->fieldname, strlen(rc->fieldname), 0, NULL); char *s = *(char **)rc->variable; assert(s != NULL); assert((intptr_t)s != -1); s = backslashify((uint8_t *)s, strlen(s), 0, NULL); if ((f == NULL) || (s == NULL)) pd_message("%s can't be displayed", rc->fieldname); else pd_message("%s is bound to \"%s\"", f, s); free(f); free(s); } else pd_message("%s: can't display value", rc->fieldname); } static int handle_prompt_enter(class md& md) { struct prompt_parse pp; struct prompt *p = &prompt.status; size_t i; int ret; bool binding_tried = false; if (prompt_parse(p, &pp) == NULL) return PROMPT_RET_ERROR; if (pp.argc == 0) { ret = PROMPT_RET_EXIT; goto end; } ret = 0; // Look for a command with that name. for (i = 0; (prompt_command[i].name != NULL); ++i) { int cret; if (strcasecmp(prompt_command[i].name, (char *)pp.argv[0])) continue; cret = prompt_command[i].cmd(md, pp.argc, (const char **)pp.argv); if ((cret & ~CMD_MSG) == CMD_ERROR) ret |= PROMPT_RET_ERROR; if (cret & CMD_MSG) ret |= PROMPT_RET_MSG; else if (cret & CMD_FAIL) { pd_message("%s: command failed", (char *)pp.argv[0]); ret |= PROMPT_RET_MSG; } else if (cret & CMD_EINVAL) { pd_message("%s: invalid argument", (char *)pp.argv[0]); ret |= PROMPT_RET_MSG; } goto end; } binding_retry: // Look for a variable with that name. for (i = 0; (rc_fields[i].fieldname != NULL); ++i) { intptr_t potential; if (strcasecmp(rc_fields[i].fieldname, (char *)pp.argv[0])) continue; // Display current value? if (pp.argv[1] == NULL) { prompt_show_rc_field(&rc_fields[i]); ret |= PROMPT_RET_MSG; break; } // Parse and set value. potential = rc_fields[i].parser((char *)pp.argv[1], rc_fields[i].variable); if ((rc_fields[i].parser != rc_number) && (potential == -1)) { pd_message("%s: invalid value", (char *)pp.argv[0]); ret |= PROMPT_RET_MSG; break; } if ((rc_fields[i].parser == rc_string) || (rc_fields[i].parser == rc_rom_path)) { struct rc_str *rs; rs = (struct rc_str *)rc_fields[i].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 *(rc_fields[i].variable) = potential; ret |= prompt_rehash_rc_field(&rc_fields[i], md); break; } if (rc_fields[i].fieldname == NULL) { if ((binding_tried == false) && (rc_binding_add((char *)pp.argv[0], "") != NULL)) { binding_tried = true; goto binding_retry; } pd_message("%s: unknown command", (char *)pp.argv[0]); ret |= PROMPT_RET_MSG; } end: prompt_parse_clean(&pp); prompt_push(p); ret |= PROMPT_RET_ENTER; return ret; } static void handle_prompt_complete_clear() { complete_path_free(prompt.complete); prompt.complete = NULL; prompt.skip = 0; prompt.common = 0; } static int handle_prompt_complete(class md& md, bool rwd) { struct prompt_parse pp; struct prompt *p = &prompt.status; size_t prompt_common = 0; // escaped version of prompt.common unsigned int skip; size_t i; const char *arg; unsigned int alen; char *s = NULL; if (prompt_parse(p, &pp) == NULL) return PROMPT_RET_ERROR; if (rwd) prompt.skip -= 2; if (pp.index == 0) { const char *cs = NULL; const char *cm = NULL; size_t common; unsigned int tmp; assert(prompt.complete == NULL); // The first argument needs to be completed. This is either // a command or a variable name. arg = (const char *)pp.argv[0]; alen = pp.cursor; if ((arg == NULL) || (alen == ~0u)) { arg = ""; alen = 0; } common = ~0u; complete_cmd_var: skip = prompt.skip; for (i = 0; (prompt_command[i].name != NULL); ++i) { if (strncasecmp(prompt_command[i].name, arg, alen)) continue; if (cm == NULL) tmp = strlen(prompt_command[i].name); else tmp = strcommon(prompt_command[i].name, cm); cm = prompt_command[i].name; if (tmp < common) common = tmp; if (skip != 0) { --skip; continue; } if (cs == NULL) cs = prompt_command[i].name; if (common == 0) goto complete_cmd_found; } // Variables. for (i = 0; (rc_fields[i].fieldname != NULL); ++i) { if (strncasecmp(rc_fields[i].fieldname, arg, alen)) continue; if (cm == NULL) tmp = strlen(rc_fields[i].fieldname); else tmp = strcommon(rc_fields[i].fieldname, cm); cm = rc_fields[i].fieldname; if (tmp < common) common = tmp; if (skip != 0) { --skip; continue; } if (cs == NULL) cs = rc_fields[i].fieldname; if (common == 0) break; } if (cs == NULL) { // Nothing matched, try again if possible. if (prompt.skip) { prompt.skip = 0; goto complete_cmd_var; } goto end; } complete_cmd_found: ++prompt.skip; s = backslashify((const uint8_t *)cs, strlen(cs), 0, &common); if (s == NULL) goto end; if (common != ~0u) { prompt.common = common; prompt_common = common; } goto replace; } // Complete function arguments. for (i = 0; (prompt_command[i].name != NULL); ++i) { char *t; if (strcasecmp(prompt_command[i].name, (const char *)pp.argv[0])) continue; if (prompt_command[i].cmpl == NULL) goto end; t = prompt_command[i].cmpl(md, pp.argc, (const char **)pp.argv, pp.cursor); if (t == NULL) goto end; prompt_common = prompt.common; s = backslashify((const uint8_t *)t, strlen(t), 0, &prompt_common); free(t); if (s == NULL) goto end; goto replace; } // Variable value completion. arg = (const char *)pp.argv[pp.index]; alen = pp.cursor; if ((arg == NULL) || (alen == ~0u)) { arg = ""; alen = 0; } for (i = 0; (rc_fields[i].fieldname != NULL); ++i) { struct rc_field *rc = &rc_fields[i]; const char **names; if (strcasecmp(rc->fieldname, (const char *)pp.argv[0])) continue; // Boolean values. if (rc->parser == rc_boolean) s = strdup((prompt.skip & 1) ? "true" : "false"); // ROM path. else if (rc->parser == rc_rom_path) { if (prompt.complete == NULL) { prompt.complete = complete_path(arg, alen, NULL); prompt.skip = 0; rehash_prompt_complete_common(); } if (prompt.complete != NULL) { char **ret = prompt.complete; rc_rom_path_retry: skip = prompt.skip; for (i = 0; (ret[i] != NULL); ++i) { if (skip == 0) break; --skip; } if (ret[i] == NULL) { if (prompt.skip != 0) { prompt.skip = 0; goto rc_rom_path_retry; } } else { prompt_common = prompt.common; s = backslashify ((const uint8_t *)ret[i], strlen(ret[i]), 0, &prompt_common); } } } // Numbers. else if ((rc->parser == rc_number) || (rc->parser == rc_soundrate)) { char buf[10]; rc_number_retry: if (snprintf(buf, sizeof(buf), "%d", (int)prompt.skip) >= (int)sizeof(buf)) { prompt.skip = 0; goto rc_number_retry; } s = strdup(buf); } // CTV filters, scaling algorithms, Z80, M68K. else if ((names = ctv_names, rc->parser == rc_ctv) || (names = scaling_names, rc->parser == rc_scaling) || (names = emu_z80_names, rc->parser == rc_emu_z80) || (names = emu_m68k_names, rc->parser == rc_emu_m68k)) { rc_names_retry: skip = prompt.skip; for (i = 0; (names[i] != NULL); ++i) { if (skip == 0) break; --skip; } if (names[i] == NULL) { if (prompt.skip != 0) { prompt.skip = 0; goto rc_names_retry; } } else s = strdup(names[i]); } if (s == NULL) break; ++prompt.skip; goto replace; } goto end; replace: prompt_replace(p, pp.argo[pp.index].pos, pp.argo[pp.index].len, (const uint8_t *)s, strlen(s)); if (prompt_common) { unsigned int cursor; cursor = (pp.argo[pp.index].pos + prompt_common); if (cursor > p->history[(p->current)].length) cursor = p->history[(p->current)].length; if (cursor != p->cursor) { p->cursor = cursor; handle_prompt_complete_clear(); } } end: free(s); prompt_parse_clean(&pp); return 0; } static int handle_prompt(uint32_t ksym, uint16_t ksym_uni, md& megad) { struct prompt::prompt_history *ph; size_t sz; uint8_t c[6]; char *s; int ret = PROMPT_RET_CONT; struct prompt *p = &prompt.status; struct prompt_parse pp; if (ksym == 0) goto end; switch (ksym & ~KEYSYM_MOD_MASK) { case SDLK_UP: handle_prompt_complete_clear(); prompt_older(p); break; case SDLK_DOWN: handle_prompt_complete_clear(); prompt_newer(p); break; case SDLK_LEFT: handle_prompt_complete_clear(); prompt_left(p); break; case SDLK_RIGHT: handle_prompt_complete_clear(); prompt_right(p); break; case SDLK_HOME: handle_prompt_complete_clear(); prompt_begin(p); break; case SDLK_END: handle_prompt_complete_clear(); prompt_end(p); break; case SDLK_BACKSPACE: handle_prompt_complete_clear(); prompt_backspace(p); break; case SDLK_DELETE: handle_prompt_complete_clear(); prompt_delete(p); break; case SDLK_a: // ^A if ((ksym & KEYSYM_MOD_CTRL) == 0) goto other; handle_prompt_complete_clear(); prompt_begin(p); break; case SDLK_e: // ^E if ((ksym & KEYSYM_MOD_CTRL) == 0) goto other; handle_prompt_complete_clear(); prompt_end(p); break; case SDLK_u: // ^U if ((ksym & KEYSYM_MOD_CTRL) == 0) goto other; handle_prompt_complete_clear(); prompt_clear(p); break; case SDLK_k: // ^K if ((ksym & KEYSYM_MOD_CTRL) == 0) goto other; handle_prompt_complete_clear(); prompt_replace(p, p->cursor, ~0u, NULL, 0); break; case SDLK_w: // ^W if ((ksym & KEYSYM_MOD_CTRL) == 0) goto other; if (prompt_parse(p, &pp) == NULL) break; if (pp.argv[pp.index] == NULL) { if (pp.index == 0) { prompt_parse_clean(&pp); break; } --pp.index; } handle_prompt_complete_clear(); if (pp.argv[(pp.index + 1)] != NULL) prompt_replace(p, pp.argo[pp.index].pos, (pp.argo[(pp.index + 1)].pos - pp.argo[pp.index].pos), NULL, 0); else prompt_replace(p, pp.argo[pp.index].pos, ~0u, NULL, 0); p->cursor = pp.argo[pp.index].pos; prompt_parse_clean(&pp); break; case SDLK_RETURN: case SDLK_KP_ENTER: handle_prompt_complete_clear(); ret |= handle_prompt_enter(megad); break; case SDLK_ESCAPE: handle_prompt_complete_clear(); ret |= PROMPT_RET_EXIT; break; case SDLK_TAB: if (ksym & KEYSYM_MOD_SHIFT) ret |= handle_prompt_complete(megad, true); else ret |= handle_prompt_complete(megad, false); break; default: other: if (ksym_uni == 0) break; handle_prompt_complete_clear(); sz = utf32u8(c, ksym_uni); if ((sz != 0) && ((s = backslashify(c, sz, BACKSLASHIFY_NOQUOTES, NULL)) != NULL)) { size_t i; for (i = 0; (i != strlen(s)); ++i) prompt_put(p, s[i]); free(s); } break; } end: if ((ret & ~(PROMPT_RET_CONT | PROMPT_RET_ENTER)) == 0) { ph = &p->history[(p->current)]; pd_message_cursor((p->cursor + 1), "%s%.*s", prompt_str, ph->length, ph->line); } return ret; } // Controls enum. You must add new entries at the end. Do not change the order. enum ctl_e { CTL_PAD1_UP, CTL_PAD1_DOWN, CTL_PAD1_LEFT, CTL_PAD1_RIGHT, CTL_PAD1_A, CTL_PAD1_B, CTL_PAD1_C, CTL_PAD1_X, CTL_PAD1_Y, CTL_PAD1_Z, CTL_PAD1_MODE, CTL_PAD1_START, CTL_PAD2_UP, CTL_PAD2_DOWN, CTL_PAD2_LEFT, CTL_PAD2_RIGHT, CTL_PAD2_A, CTL_PAD2_B, CTL_PAD2_C, CTL_PAD2_X, CTL_PAD2_Y, CTL_PAD2_Z, CTL_PAD2_MODE, CTL_PAD2_START, #ifdef WITH_PICO CTL_PICO_PEN_UP, CTL_PICO_PEN_DOWN, CTL_PICO_PEN_LEFT, CTL_PICO_PEN_RIGHT, CTL_PICO_PEN_BUTTON, #endif CTL_DGEN_QUIT, CTL_DGEN_CRAPTV_TOGGLE, CTL_DGEN_SCALING_TOGGLE, CTL_DGEN_RESET, CTL_DGEN_SLOT0, CTL_DGEN_SLOT1, CTL_DGEN_SLOT2, CTL_DGEN_SLOT3, CTL_DGEN_SLOT4, CTL_DGEN_SLOT5, CTL_DGEN_SLOT6, CTL_DGEN_SLOT7, CTL_DGEN_SLOT8, CTL_DGEN_SLOT9, CTL_DGEN_SLOT_NEXT, CTL_DGEN_SLOT_PREV, CTL_DGEN_SAVE, CTL_DGEN_LOAD, CTL_DGEN_Z80_TOGGLE, CTL_DGEN_CPU_TOGGLE, CTL_DGEN_STOP, CTL_DGEN_PROMPT, CTL_DGEN_GAME_GENIE, CTL_DGEN_VOLUME_INC, CTL_DGEN_VOLUME_DEC, CTL_DGEN_FULLSCREEN_TOGGLE, CTL_DGEN_FIX_CHECKSUM, CTL_DGEN_SCREENSHOT, CTL_DGEN_DEBUG_ENTER, CTL_ }; // Controls definitions. struct ctl { const enum ctl_e type; intptr_t (*const rc)[RCB_NUM]; int (*const press)(struct ctl&, md&); int (*const release)(struct ctl&, md&); #define DEF 0, 0, 0, 0 unsigned int pressed:1; unsigned int coord:1; unsigned int x:10; unsigned int y:10; }; static int ctl_pad1(struct ctl& ctl, md& megad) { switch (ctl.type) { case CTL_PAD1_UP: megad.pad[0] &= ~MD_UP_MASK; break; case CTL_PAD1_DOWN: megad.pad[0] &= ~MD_DOWN_MASK; break; case CTL_PAD1_LEFT: megad.pad[0] &= ~MD_LEFT_MASK; break; case CTL_PAD1_RIGHT: megad.pad[0] &= ~MD_RIGHT_MASK; break; case CTL_PAD1_A: megad.pad[0] &= ~MD_A_MASK; break; case CTL_PAD1_B: megad.pad[0] &= ~MD_B_MASK; break; case CTL_PAD1_C: megad.pad[0] &= ~MD_C_MASK; break; case CTL_PAD1_X: megad.pad[0] &= ~MD_X_MASK; break; case CTL_PAD1_Y: megad.pad[0] &= ~MD_Y_MASK; break; case CTL_PAD1_Z: megad.pad[0] &= ~MD_Z_MASK; break; case CTL_PAD1_MODE: megad.pad[0] &= ~MD_MODE_MASK; break; case CTL_PAD1_START: megad.pad[0] &= ~MD_START_MASK; break; default: break; } return 1; } static int ctl_pad1_release(struct ctl& ctl, md& megad) { switch (ctl.type) { case CTL_PAD1_UP: megad.pad[0] |= MD_UP_MASK; break; case CTL_PAD1_DOWN: megad.pad[0] |= MD_DOWN_MASK; break; case CTL_PAD1_LEFT: megad.pad[0] |= MD_LEFT_MASK; break; case CTL_PAD1_RIGHT: megad.pad[0] |= MD_RIGHT_MASK; break; case CTL_PAD1_A: megad.pad[0] |= MD_A_MASK; break; case CTL_PAD1_B: megad.pad[0] |= MD_B_MASK; break; case CTL_PAD1_C: megad.pad[0] |= MD_C_MASK; break; case CTL_PAD1_X: megad.pad[0] |= MD_X_MASK; break; case CTL_PAD1_Y: megad.pad[0] |= MD_Y_MASK; break; case CTL_PAD1_Z: megad.pad[0] |= MD_Z_MASK; break; case CTL_PAD1_MODE: megad.pad[0] |= MD_MODE_MASK; break; case CTL_PAD1_START: megad.pad[0] |= MD_START_MASK; break; default: break; } return 1; } static int ctl_pad2(struct ctl& ctl, md& megad) { switch (ctl.type) { case CTL_PAD2_UP: megad.pad[1] &= ~MD_UP_MASK; break; case CTL_PAD2_DOWN: megad.pad[1] &= ~MD_DOWN_MASK; break; case CTL_PAD2_LEFT: megad.pad[1] &= ~MD_LEFT_MASK; break; case CTL_PAD2_RIGHT: megad.pad[1] &= ~MD_RIGHT_MASK; break; case CTL_PAD2_A: megad.pad[1] &= ~MD_A_MASK; break; case CTL_PAD2_B: megad.pad[1] &= ~MD_B_MASK; break; case CTL_PAD2_C: megad.pad[1] &= ~MD_C_MASK; break; case CTL_PAD2_X: megad.pad[1] &= ~MD_X_MASK; break; case CTL_PAD2_Y: megad.pad[1] &= ~MD_Y_MASK; break; case CTL_PAD2_Z: megad.pad[1] &= ~MD_Z_MASK; break; case CTL_PAD2_MODE: megad.pad[1] &= ~MD_MODE_MASK; break; case CTL_PAD2_START: megad.pad[1] &= ~MD_START_MASK; break; default: break; } return 1; } static int ctl_pad2_release(struct ctl& ctl, md& megad) { switch (ctl.type) { case CTL_PAD2_UP: megad.pad[1] |= MD_UP_MASK; break; case CTL_PAD2_DOWN: megad.pad[1] |= MD_DOWN_MASK; break; case CTL_PAD2_LEFT: megad.pad[1] |= MD_LEFT_MASK; break; case CTL_PAD2_RIGHT: megad.pad[1] |= MD_RIGHT_MASK; break; case CTL_PAD2_A: megad.pad[1] |= MD_A_MASK; break; case CTL_PAD2_B: megad.pad[1] |= MD_B_MASK; break; case CTL_PAD2_C: megad.pad[1] |= MD_C_MASK; break; case CTL_PAD2_X: megad.pad[1] |= MD_X_MASK; break; case CTL_PAD2_Y: megad.pad[1] |= MD_Y_MASK; break; case CTL_PAD2_Z: megad.pad[1] |= MD_Z_MASK; break; case CTL_PAD2_MODE: megad.pad[1] |= MD_MODE_MASK; break; case CTL_PAD2_START: megad.pad[1] |= MD_START_MASK; break; default: break; } return 1; } #ifdef WITH_PICO static int ctl_pico_pen(struct ctl& ctl, md& megad) { static unsigned int min_y = 0x1fc; static unsigned int max_y = 0x2f7; static unsigned int min_x = 0x3c; static unsigned int max_x = 0x17c; static const struct { enum ctl_e type; unsigned int coords:1; unsigned int dir:1; unsigned int lim[2]; } motion[] = { { CTL_PICO_PEN_UP, 1, 0, { min_y, max_y } }, { CTL_PICO_PEN_DOWN, 1, 1, { min_y, max_y } }, { CTL_PICO_PEN_LEFT, 0, 0, { min_x, max_x } }, { CTL_PICO_PEN_RIGHT, 0, 1, { min_x, max_x } } }; unsigned int i; if (ctl.type == CTL_PICO_PEN_BUTTON) { megad.pad[0] &= ~MD_PICO_PENBTN_MASK; return 1; } // Use coordinates if available. if ((ctl.coord) && (screen.window_width != 0) && (screen.window_height != 0)) { megad.pico_pen_coords[1] = (min_y + ((ctl.y * (max_y - min_y)) / screen.window_height)); megad.pico_pen_coords[0] = (min_x + ((ctl.x * (max_x - min_x)) / screen.window_width)); return 1; } for (i = 0; (i != elemof(motion)); ++i) { unsigned int coords; if (motion[i].type != ctl.type) continue; coords = motion[i].coords; if (motion[i].dir) megad.pico_pen_coords[coords] += pico_pen_stride; else megad.pico_pen_coords[coords] -= pico_pen_stride; if ((megad.pico_pen_coords[coords] < motion[i].lim[0]) || (megad.pico_pen_coords[coords] > motion[i].lim[1])) megad.pico_pen_coords[coords] = motion[i].lim[motion[i].dir]; break; } return 1; } static int ctl_pico_pen_release(struct ctl& ctl, md& megad) { if (ctl.type == CTL_PICO_PEN_BUTTON) megad.pad[0] |= MD_PICO_PENBTN_MASK; return 1; } #endif static int ctl_dgen_quit(struct ctl&, md&) { return 0; } static int ctl_dgen_craptv_toggle(struct ctl&, md&) { #ifdef WITH_CTV dgen_craptv = ((dgen_craptv + 1) % NUM_CTV); filters_pluck_ctv(); filters_insert(filters_find(ctv_names[dgen_craptv])); pd_message("Crap TV mode \"%s\".", ctv_names[dgen_craptv]); #endif // WITH_CTV return 1; } static int ctl_dgen_scaling_toggle(struct ctl&, md&) { dgen_scaling = ((dgen_scaling + 1) % NUM_SCALING); if (set_scaling(scaling_names[dgen_scaling])) pd_message("Scaling algorithm \"%s\" unavailable.", scaling_names[dgen_scaling]); else pd_message("Using scaling algorithm \"%s\".", scaling_names[dgen_scaling]); return 1; } static int ctl_dgen_reset(struct ctl&, md& megad) { megad.reset(); pd_message("Genesis reset."); return 1; } static int ctl_dgen_slot(struct ctl& ctl, md&) { slot = ((int)ctl.type - CTL_DGEN_SLOT0); pd_message("Selected save slot %d.", slot); return 1; } static int ctl_dgen_slot_next(struct ctl&, md&) { if (slot == 9) slot = 0; else slot++; pd_message("Selected next save slot (%d).", slot); return 1; } static int ctl_dgen_slot_prev(struct ctl&, md&) { if (slot == 0) slot = 9; else slot--; pd_message("Selected previous save slot (%d).", slot); return 1; } static int ctl_dgen_save(struct ctl&, md& megad) { md_save(megad); return 1; } static int ctl_dgen_load(struct ctl&, md& megad) { md_load(megad); return 1; } // Cycle Z80 core. static int ctl_dgen_z80_toggle(struct ctl&, md& megad) { const char *msg; megad.cycle_z80(); switch (megad.z80_core) { #ifdef WITH_CZ80 case md::Z80_CORE_CZ80: msg = "CZ80 core activated."; break; #endif #ifdef WITH_MZ80 case md::Z80_CORE_MZ80: msg = "MZ80 core activated."; break; #endif #ifdef WITH_DRZ80 case md::Z80_CORE_DRZ80: msg = "DrZ80 core activated."; break; #endif default: msg = "Z80 core disabled."; break; } pd_message(msg); return 1; } // Added this CPU core hot swap. Compile both Musashi and StarScream // in, and swap on the fly like DirectX DGen. [PKH] static int ctl_dgen_cpu_toggle(struct ctl&, md& megad) { const char *msg; megad.cycle_cpu(); switch (megad.cpu_emu) { #ifdef WITH_STAR case md::CPU_EMU_STAR: msg = "StarScream CPU core activated."; break; #endif #ifdef WITH_MUSA case md::CPU_EMU_MUSA: msg = "Musashi CPU core activated."; break; #endif #ifdef WITH_CYCLONE case md::CPU_EMU_CYCLONE: msg = "Cyclone CPU core activated."; break; #endif default: msg = "CPU core disabled."; break; } pd_message(msg); return 1; } static int ctl_dgen_stop(struct ctl&, md& megad) { pd_message(stopped_str); if (stop_events(megad, STOPPED) != 0) return 0; return 1; } static int ctl_dgen_prompt(struct ctl&, md& megad) { pd_message_cursor(strlen(prompt_str), prompt_str); if (stop_events(megad, PROMPT) != 0) return 0; return 1; } static int ctl_dgen_game_genie(struct ctl&, md& megad) { pd_message_cursor(strlen(game_genie_str), game_genie_str); if (stop_events(megad, GAME_GENIE) != 0) return 0; return 1; } static int ctl_dgen_volume(struct ctl& ctl, md&) { if (ctl.type == CTL_DGEN_VOLUME_INC) ++dgen_volume; else --dgen_volume; if (dgen_volume < 0) dgen_volume = 0; else if (dgen_volume > 100) dgen_volume = 100; pd_message("Volume %d%%.", (int)dgen_volume); return 1; } static int ctl_dgen_fullscreen_toggle(struct ctl&, md&) { switch (set_fullscreen(!screen.is_fullscreen)) { case -2: fprintf(stderr, "sdl: fatal error while trying to change screen" " resolution.\n"); return 0; case -1: pd_message("Failed to toggle fullscreen mode."); break; default: pd_message("Fullscreen mode toggled."); } return 1; } static int ctl_dgen_fix_checksum(struct ctl&, md& megad) { pd_message("Checksum fixed."); megad.fix_rom_checksum(); return 1; } static int ctl_dgen_screenshot(struct ctl&, md& megad) { do_screenshot(megad); return 1; } static int ctl_dgen_debug_enter(struct ctl&, md& megad) { #ifdef WITH_DEBUGGER stopped = 1; if (megad.debug_trap == false) megad.debug_enter(); else megad.debug_leave(); #else (void)megad; pd_message("Debugger support not built in."); #endif return 1; } static struct ctl control[] = { // Array indices and control[].type must match enum ctl_e's order. { CTL_PAD1_UP, &pad1_up, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_DOWN, &pad1_down, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_LEFT, &pad1_left, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_RIGHT, &pad1_right, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_A, &pad1_a, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_B, &pad1_b, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_C, &pad1_c, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_X, &pad1_x, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_Y, &pad1_y, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_Z, &pad1_z, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_MODE, &pad1_mode, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD1_START, &pad1_start, ctl_pad1, ctl_pad1_release, DEF }, { CTL_PAD2_UP, &pad2_up, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_DOWN, &pad2_down, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_LEFT, &pad2_left, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_RIGHT, &pad2_right, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_A, &pad2_a, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_B, &pad2_b, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_C, &pad2_c, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_X, &pad2_x, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_Y, &pad2_y, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_Z, &pad2_z, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_MODE, &pad2_mode, ctl_pad2, ctl_pad2_release, DEF }, { CTL_PAD2_START, &pad2_start, ctl_pad2, ctl_pad2_release, DEF }, #ifdef WITH_PICO { CTL_PICO_PEN_UP, &pico_pen_up, ctl_pico_pen, ctl_pico_pen_release, DEF }, { CTL_PICO_PEN_DOWN, &pico_pen_down, ctl_pico_pen, ctl_pico_pen_release, DEF }, { CTL_PICO_PEN_LEFT, &pico_pen_left, ctl_pico_pen, ctl_pico_pen_release, DEF }, { CTL_PICO_PEN_RIGHT, &pico_pen_right, ctl_pico_pen, ctl_pico_pen_release, DEF }, { CTL_PICO_PEN_BUTTON, &pico_pen_button, ctl_pico_pen, ctl_pico_pen_release, DEF }, #endif { CTL_DGEN_QUIT, &dgen_quit, ctl_dgen_quit, NULL, DEF }, { CTL_DGEN_CRAPTV_TOGGLE, &dgen_craptv_toggle, ctl_dgen_craptv_toggle, NULL, DEF }, { CTL_DGEN_SCALING_TOGGLE, &dgen_scaling_toggle, ctl_dgen_scaling_toggle, NULL, DEF }, { CTL_DGEN_RESET, &dgen_reset, ctl_dgen_reset, NULL, DEF }, { CTL_DGEN_SLOT0, &dgen_slot_0, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT1, &dgen_slot_1, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT2, &dgen_slot_2, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT3, &dgen_slot_3, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT4, &dgen_slot_4, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT5, &dgen_slot_5, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT6, &dgen_slot_6, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT7, &dgen_slot_7, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT8, &dgen_slot_8, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT9, &dgen_slot_9, ctl_dgen_slot, NULL, DEF }, { CTL_DGEN_SLOT_NEXT, &dgen_slot_next, ctl_dgen_slot_next, NULL, DEF }, { CTL_DGEN_SLOT_PREV, &dgen_slot_prev, ctl_dgen_slot_prev, NULL, DEF }, { CTL_DGEN_SAVE, &dgen_save, ctl_dgen_save, NULL, DEF }, { CTL_DGEN_LOAD, &dgen_load, ctl_dgen_load, NULL, DEF }, { CTL_DGEN_Z80_TOGGLE, &dgen_z80_toggle, ctl_dgen_z80_toggle, NULL, DEF }, { CTL_DGEN_CPU_TOGGLE, &dgen_cpu_toggle, ctl_dgen_cpu_toggle, NULL, DEF }, { CTL_DGEN_STOP, &dgen_stop, ctl_dgen_stop, NULL, DEF }, { CTL_DGEN_PROMPT, &dgen_prompt, ctl_dgen_prompt, NULL, DEF }, { CTL_DGEN_GAME_GENIE, &dgen_game_genie, ctl_dgen_game_genie, NULL, DEF }, { CTL_DGEN_VOLUME_INC, &dgen_volume_inc, ctl_dgen_volume, NULL, DEF }, { CTL_DGEN_VOLUME_DEC, &dgen_volume_dec, ctl_dgen_volume, NULL, DEF }, { CTL_DGEN_FULLSCREEN_TOGGLE, &dgen_fullscreen_toggle, ctl_dgen_fullscreen_toggle, NULL, DEF }, { CTL_DGEN_FIX_CHECKSUM, &dgen_fix_checksum, ctl_dgen_fix_checksum, NULL, DEF }, { CTL_DGEN_SCREENSHOT, &dgen_screenshot, ctl_dgen_screenshot, NULL, DEF }, { CTL_DGEN_DEBUG_ENTER, &dgen_debug_enter, ctl_dgen_debug_enter, NULL, DEF }, { CTL_, NULL, NULL, NULL, DEF } }; static struct { char const* name; ///< Controller button name. enum ctl_e const id[2]; ///< Controls indices in control[]. bool once; ///< If button has been pressed once. bool twice; ///< If button has been pressed twice. enum rc_binding_type type; ///< Type of code. intptr_t code; ///< Temporary code. } calibration_steps[] = { { "START", { CTL_PAD1_START, CTL_PAD2_START }, false, false, RCB_NUM, -1 }, { "MODE", { CTL_PAD1_MODE, CTL_PAD2_MODE }, false, false, RCB_NUM, -1 }, { "A", { CTL_PAD1_A, CTL_PAD2_A }, false, false, RCB_NUM, -1 }, { "B", { CTL_PAD1_B, CTL_PAD2_B }, false, false, RCB_NUM, -1 }, { "C", { CTL_PAD1_C, CTL_PAD2_C }, false, false, RCB_NUM, -1 }, { "X", { CTL_PAD1_X, CTL_PAD2_X }, false, false, RCB_NUM, -1 }, { "Y", { CTL_PAD1_Y, CTL_PAD2_Y }, false, false, RCB_NUM, -1 }, { "Z", { CTL_PAD1_Z, CTL_PAD2_Z }, false, false, RCB_NUM, -1 }, { "UP", { CTL_PAD1_UP, CTL_PAD2_UP }, false, false, RCB_NUM, -1 }, { "DOWN", { CTL_PAD1_DOWN, CTL_PAD2_DOWN }, false, false, RCB_NUM, -1 }, { "LEFT", { CTL_PAD1_LEFT, CTL_PAD2_LEFT }, false, false, RCB_NUM, -1 }, { "RIGHT", { CTL_PAD1_RIGHT, CTL_PAD2_RIGHT }, false, false, RCB_NUM, -1 }, { NULL, { CTL_, CTL_ }, false, false, RCB_NUM, -1 } }; /** * Handle input during calibration process. * @param type Type of code. * @param code Code to process. */ static void manage_calibration(enum rc_binding_type type, intptr_t code) { unsigned int step = 0; assert(calibrating_controller < 2); if (!calibrating) { // Stop emulation, enter calibration mode. freeze(true); calibrating = true; filter_text_str[0] = '\0'; filter_text_msg(FILTER_TEXT_BG_BLACK FILTER_TEXT_CENTER FILTER_TEXT_8X13 "CONTROLLER %u CALIBRATION\n" "\n" FILTER_TEXT_7X6 FILTER_TEXT_LEFT "Press each button twice,\n" "or two different buttons to skip them.\n" "\n", (calibrating_controller + 1)); filters_pluck(&filter_text_def); filters_insert(&filter_text_def); goto ask; } while (step != elemof(calibration_steps)) if ((calibration_steps[step].once == true) && (calibration_steps[step].twice == true)) ++step; else break; if (step == elemof(calibration_steps)) { // Reset everything. for (step = 0; (step != elemof(calibration_steps)); ++step) { calibration_steps[step].once = false; calibration_steps[step].twice = false; calibration_steps[step].type = RCB_NUM; calibration_steps[step].code = -1; } // Restart emulation. freeze(false); calibrating = false; filters_pluck(&filter_text_def); return; } if (calibration_steps[step].once == false) { char *dump; if (type == RCBK) dump = dump_keysym(code); else if (type == RCBJ) dump = dump_joypad(code); else if (type == RCBM) dump = dump_mouse(code); else dump = NULL; assert(calibration_steps[step].twice == false); calibration_steps[step].once = true; calibration_steps[step].type = type; calibration_steps[step].code = code; filter_text_msg("\"%s\", confirm: ", (dump ? dump : "")); free(dump); } else if (calibration_steps[step].twice == false) { calibration_steps[step].twice = true; if ((calibration_steps[step].type == type) && (calibration_steps[step].code == code)) filter_text_msg("OK\n"); else { calibration_steps[step].type = RCB_NUM; calibration_steps[step].code = -1; filter_text_msg("none\n"); } } if ((calibration_steps[step].once != true) || (calibration_steps[step].twice != true)) return; ++step; ask: if (step == elemof(calibration_steps)) { code = calibration_steps[(elemof(calibration_steps) - 1)].code; if (code == -1) filter_text_msg("\n" "Aborted."); else { unsigned int i; for (i = 0; (i != elemof(calibration_steps)); ++i) { enum ctl_e id; id = calibration_steps[i].id [calibrating_controller]; type = calibration_steps[i].type; code = calibration_steps[i].code; assert((size_t)id < elemof(control)); assert(control[id].type == id); if ((id != CTL_) && (type != RCB_NUM)) (*control[id].rc)[type] = code; } filter_text_msg("\n" "Applied."); } } else if (calibration_steps[step].name != NULL) filter_text_msg("%s: ", calibration_steps[step].name); else filter_text_msg("\n" "Press any button twice to apply settings:\n" ""); } static struct rc_binding_item combos[64]; static void manage_combos(md& md, bool pressed, enum rc_binding_type type, intptr_t code) { unsigned int i; (void)md; for (i = 0; (i != elemof(combos)); ++i) { if (!combos[i].assigned) { if (!pressed) return; // Not in the list, nothing to do. // Not found, add it to the list. combos[i].assigned = true; combos[i].type = type; combos[i].code = code; return; } if ((combos[i].type != type) || (combos[i].code != code)) continue; // Does not match. if (pressed) return; // Already pressed. // Release entry. memmove(&combos[i], &combos[i + 1], ((elemof(combos) - (i + 1)) * sizeof(combos[i]))); break; } } static bool check_combos(md& md, struct rc_binding_item item[], unsigned int num) { unsigned int i; unsigned int found = 0; (void)md; for (i = 0; (i != num); ++i) { unsigned int j; if (!item[i].assigned) { num = i; break; } for (j = 0; (j != elemof(combos)); ++j) { if (!combos[j].assigned) break; if ((combos[j].type != item[i].type) || (combos[j].code != item[i].code)) continue; ++found; break; } } if (num == 0) return false; return (found == num); } static int manage_bindings(md& md, bool pressed, enum rc_binding_type type, intptr_t code) { struct rc_binding *rcb = rc_binding_head.next; size_t pos = 0; size_t seek = 0; if ((dgen_buttons) && (pressed)) { char *dump; if (type == RCBK) dump = dump_keysym(code); else if (type == RCBJ) dump = dump_joypad(code); else if (type == RCBM) dump = dump_mouse(code); else dump = NULL; if (dump != NULL) { pd_message("Pressed \"%s\".", dump); free(dump); } } while (rcb != &rc_binding_head) { if ((pos < seek) || (!check_combos(md, rcb->item, elemof(rcb->item)))) { ++pos; rcb = rcb->next; continue; } assert(rcb->to != NULL); assert((intptr_t)rcb->to != -1); // For keyboard and joystick bindings, perform related action. if ((type = RCBK, !strncasecmp("key_", rcb->to, 4)) || (type = RCBJ, !strncasecmp("joy_", rcb->to, 4)) || (type = RCBM, !strncasecmp("mou_", rcb->to, 4))) { struct rc_field *rcf = rc_fields; while (rcf->fieldname != NULL) { struct ctl *ctl = control; if (strcasecmp(rcb->to, rcf->fieldname)) { ++rcf; continue; } while (ctl->rc != NULL) { if (&(*ctl->rc)[type] != rcf->variable) { ++ctl; continue; } // Got it, finally. if (pressed) { assert(ctl->press != NULL); if (!ctl->press(*ctl, md)) return 0; } else if (ctl->release != NULL) { if (!ctl->release(*ctl, md)) return 0; } break; } break; } } // Otherwise, pass it to the prompt. else if (pressed) { handle_prompt_complete_clear(); prompt_replace(&prompt.status, 0, 0, (uint8_t *)rcb->to, strlen(rcb->to)); if (handle_prompt_enter(md) & PROMPT_RET_ERROR) return 0; } // In case the current (or any other binding) has been // removed, rewind and seek to the next position. rcb = rc_binding_head.next; seek = (pos + 1); pos = 0; } return 1; } static int manage_game_genie(md& megad, intptr_t ksym, intptr_t ksym_uni) { static char buf[12]; static kb_input_t input = { buf, 0, sizeof(buf) }; unsigned int len = strlen(game_genie_str); switch (kb_input(&input, ksym, ksym_uni)) { unsigned int errors; unsigned int applied; unsigned int reverted; case KB_INPUT_ENTERED: megad.patch(input.buf, &errors, &applied, &reverted); if (errors) pd_message("Invalid code."); else if (reverted) pd_message("Reverted."); else if (applied) pd_message("Applied."); else { case KB_INPUT_ABORTED: pd_message("Aborted."); } goto over; case KB_INPUT_CONSUMED: pd_message_cursor((len + input.pos), "%s%.*s", game_genie_str, (int)input.pos, buf); break; case KB_INPUT_IGNORED: break; } return 0; over: input.buf = buf; input.pos = 0; input.size = sizeof(buf); memset(buf, 0, sizeof(buf)); return 1; } #ifdef WITH_PICO static void manage_pico_pen(md& megad) { static unsigned long pico_pen_last_update; unsigned long pico_pen_now; if (!megad.pico_enabled) return; // Repeat pen motion as long as buttons are not released. // This is not necessary when pen is managed by direct coordinates. if ((((control[CTL_PICO_PEN_UP].pressed) && (!control[CTL_PICO_PEN_UP].coord)) || ((control[CTL_PICO_PEN_DOWN].pressed) && (!control[CTL_PICO_PEN_DOWN].coord)) || ((control[CTL_PICO_PEN_LEFT].pressed) && (!control[CTL_PICO_PEN_LEFT].coord)) || ((control[CTL_PICO_PEN_RIGHT].pressed) && (!control[CTL_PICO_PEN_RIGHT].coord))) && (pico_pen_now = pd_usecs(), ((pico_pen_now - pico_pen_last_update) >= ((unsigned long)pico_pen_delay * 1000)))) { if (control[CTL_PICO_PEN_UP].pressed) ctl_pico_pen (control[CTL_PICO_PEN_UP], megad); if (control[CTL_PICO_PEN_DOWN].pressed) ctl_pico_pen (control[CTL_PICO_PEN_DOWN], megad); if (control[CTL_PICO_PEN_LEFT].pressed) ctl_pico_pen (control[CTL_PICO_PEN_LEFT], megad); if (control[CTL_PICO_PEN_RIGHT].pressed) ctl_pico_pen (control[CTL_PICO_PEN_RIGHT], megad); pico_pen_last_update = pico_pen_now; } } #endif static bool mouse_is_grabbed() { return (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON); } static void mouse_grab(bool grab) { SDL_GrabMode mode = SDL_WM_GrabInput(SDL_GRAB_QUERY); if ((grab) && (!pd_freeze) && (mode == SDL_GRAB_OFF)) { // Hide the cursor. SDL_ShowCursor(0); pd_message("Mouse trapped. Stop emulation to release."); SDL_WM_GrabInput(SDL_GRAB_ON); } else if ((!grab) && (mode == SDL_GRAB_ON)) { SDL_ShowCursor(1); SDL_WM_GrabInput(SDL_GRAB_OFF); } } static int stop_events(md& megad, enum events status) { struct ctl* ctl; stopped = 1; freeze(true); events = status; // Release controls. for (ctl = control; (ctl->rc != NULL); ++ctl) { if (ctl->pressed == false) continue; ctl->pressed = false; ctl->coord = false; if ((ctl->release != NULL) && (ctl->release(*ctl, megad) == 0)) return -1; // XXX do something about this. } SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // Switch out of fullscreen mode (assuming this is supported) if (screen.is_fullscreen) { if (set_fullscreen(0) < -1) return -1; pd_graphics_update(true); } mouse_grab(false); return 0; } static void restart_events(md& megad) { (void)megad; stopped = 1; freeze(false); handle_prompt_complete_clear(); SDL_EnableKeyRepeat(0, 0); events = STARTED; } static struct { unsigned long when[0x100]; uint8_t enabled[0x100 / 8]; unsigned int count; } mouse_motion_release; #define MOUSE_MOTION_RELEASE_IS_ENABLED(which) \ (mouse_motion_release.enabled[(which) / 8] & (1 << ((which) % 8))) #define MOUSE_MOTION_RELEASE_DISABLE(which) \ (mouse_motion_release.enabled[(which) / 8] &= ~(1 << ((which) % 8))) #define MOUSE_MOTION_RELEASE_ENABLE(which) \ (mouse_motion_release.enabled[(which) / 8] |= (1 << ((which) % 8))) static void mouse_motion_delay_release(unsigned int which, bool enable) { if (which >= elemof(mouse_motion_release.when)) { DEBUG(("mouse index too high (%u)", which)); return; } if (!enable) { if (!MOUSE_MOTION_RELEASE_IS_ENABLED(which)) return; MOUSE_MOTION_RELEASE_DISABLE(which); assert(mouse_motion_release.count != 0); --mouse_motion_release.count; return; } if (!MOUSE_MOTION_RELEASE_IS_ENABLED(which)) { MOUSE_MOTION_RELEASE_ENABLE(which); ++mouse_motion_release.count; assert(mouse_motion_release.count <= elemof(mouse_motion_release.when)); } mouse_motion_release.when[which] = (pd_usecs() + (dgen_mouse_delay * 1000)); } static bool mouse_motion_released(SDL_Event *event) { unsigned int i; unsigned long now; if (mouse_motion_release.count == 0) return false; now = pd_usecs(); for (i = 0; (i != mouse_motion_release.count); ++i) { unsigned long diff; if (!MOUSE_MOTION_RELEASE_IS_ENABLED(i)) continue; diff = (mouse_motion_release.when[i] - now); if (diff < (unsigned long)(dgen_mouse_delay * 1000)) continue; event->motion.type = SDL_MOUSEMOTION; event->motion.which = i; event->motion.xrel = 0; event->motion.yrel = 0; MOUSE_MOTION_RELEASE_DISABLE(i); --mouse_motion_release.count; return true; } return false; } #define MOUSE_SHOW_USECS (unsigned long)(2 * 1000000) // The massive event handler! // I know this is an ugly beast, but please don't be discouraged. If you need // help, don't be afraid to ask me how something works. Basically, just handle // all the event keys, or even ignore a few if they don't make sense for your // interface. int pd_handle_events(md &megad) { static uint16_t kpress[0x100]; #ifdef WITH_DEBUGGER static bool debug_trap; #endif static unsigned long hide_mouse_when; static bool hide_mouse; #ifdef WITH_JOYSTICK static uint32_t const axis_value[][3] = { // { pressed, [implicitly released ...] } { JS_AXIS_NEGATIVE, JS_AXIS_BETWEEN, JS_AXIS_POSITIVE }, { JS_AXIS_POSITIVE, JS_AXIS_BETWEEN, JS_AXIS_NEGATIVE }, { JS_AXIS_BETWEEN, JS_AXIS_POSITIVE, JS_AXIS_NEGATIVE } }; static uint32_t const hat_value[][2] = { // { SDL value, pressed } { SDL_HAT_UP, JS_HAT_UP }, { SDL_HAT_RIGHT, JS_HAT_RIGHT }, { SDL_HAT_DOWN, JS_HAT_DOWN }, { SDL_HAT_LEFT, JS_HAT_LEFT } }; unsigned int hat_value_map; intptr_t joypad; #endif bool pressed; uint32_t plist[8]; uint32_t rlist[8]; unsigned int i, pi, ri; SDL_Event event; uint16_t ksym_uni; intptr_t ksym; intptr_t mouse; unsigned int which; #ifdef WITH_DEBUGGER if ((megad.debug_trap) && (megad.debug_enter() < 0)) return 0; if (debug_trap != megad.debug_trap) { debug_trap = megad.debug_trap; if (debug_trap) mouse_grab(false); if (sound.cbuf.size) SDL_PauseAudio(debug_trap == true); } #endif if ((hide_mouse) && ((hide_mouse_when - pd_usecs()) >= MOUSE_SHOW_USECS)) { if (!mouse_is_grabbed()) SDL_ShowCursor(0); hide_mouse = false; } next_event: if (mouse_motion_released(&event)) goto mouse_motion; if (!SDL_PollEvent(&event)) { #ifdef WITH_PICO manage_pico_pen(megad); #endif return 1; } switch (event.type) { #ifdef WITH_JOYSTICK case SDL_JOYAXISMOTION: if (event.jaxis.value <= -16384) i = 0; else if (event.jaxis.value >= 16384) i = 1; else i = 2; plist[0] = JS_AXIS(event.jaxis.which, event.jaxis.axis, axis_value[i][0]); rlist[0] = JS_AXIS(event.jaxis.which, event.jaxis.axis, axis_value[i][1]); rlist[1] = JS_AXIS(event.jaxis.which, event.jaxis.axis, axis_value[i][2]); // "between" causes problems during calibration, ignore it. if (axis_value[i][0] == JS_AXIS_BETWEEN) pi = 0; else pi = 1; ri = 2; goto joypad_axis; case SDL_JOYHATMOTION: pi = 0; ri = 0; hat_value_map = 0; for (i = 0; (i != elemof(hat_value)); ++i) if (event.jhat.value & hat_value[i][0]) { plist[pi++] = JS_HAT(event.jhat.which, event.jhat.hat, hat_value[i][1]); hat_value_map |= (1 << i); } for (i = 0; (i != elemof(hat_value)); ++i) if ((hat_value_map & (1 << i)) == 0) rlist[ri++] = JS_HAT(event.jhat.which, event.jhat.hat, hat_value[i][1]); joypad_axis: for (i = 0; (i != ri); ++i) manage_combos(megad, false, RCBJ, rlist[i]); for (i = 0; (i != pi); ++i) manage_combos(megad, true, RCBJ, plist[i]); if (events != STARTED) break; if (calibrating) { for (i = 0; ((calibrating) && (i != pi)); ++i) manage_calibration(RCBJ, plist[i]); break; } for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) { // Release buttons first. for (i = 0; (i != ri); ++i) { if ((ctl->pressed == false) || ((uint32_t)(*ctl->rc)[RCBJ] != rlist[i])) continue; ctl->pressed = false; ctl->coord = false; if ((ctl->release != NULL) && (ctl->release(*ctl, megad) == 0)) return 0; } for (i = 0; (i != pi); ++i) { if ((uint32_t)(*ctl->rc)[RCBJ] == plist[i]) { assert(ctl->press != NULL); ctl->pressed = true; ctl->coord = false; if (ctl->press(*ctl, megad) == 0) return 0; } } } for (i = 0; (i != ri); ++i) if (!manage_bindings(megad, false, RCBJ, rlist[i])) return 0; for (i = 0; (i != pi); ++i) if (!manage_bindings(megad, true, RCBJ, plist[i])) return 0; break; case SDL_JOYBUTTONDOWN: assert(event.jbutton.state == SDL_PRESSED); pressed = true; goto joypad_button; case SDL_JOYBUTTONUP: assert(event.jbutton.state == SDL_RELEASED); pressed = false; joypad_button: joypad = JS_BUTTON(event.jbutton.which, event.jbutton.button); manage_combos(megad, pressed, RCBJ, joypad); if (events != STARTED) break; if (calibrating) { if (pressed) manage_calibration(RCBJ, joypad); break; } for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) { if ((*ctl->rc)[RCBJ] != joypad) continue; ctl->pressed = pressed; ctl->coord = false; if (pressed == false) { if ((ctl->release != NULL) && (ctl->release(*ctl, megad) == 0)) return 0; } else { assert(ctl->press != NULL); if (ctl->press(*ctl, megad) == 0) return 0; } } if (manage_bindings(megad, pressed, RCBJ, joypad) == 0) return 0; break; #endif // WITH_JOYSTICK case SDL_KEYDOWN: ksym = event.key.keysym.sym; ksym_uni = event.key.keysym.unicode; if ((ksym_uni < 0x20) || ((ksym >= SDLK_KP0) && (ksym <= SDLK_KP_EQUALS))) ksym_uni = 0; kpress[(ksym & 0xff)] = ksym_uni; if (ksym_uni) ksym = ksym_uni; else if (event.key.keysym.mod & KMOD_SHIFT) ksym |= KEYSYM_MOD_SHIFT; // Check for modifiers if (event.key.keysym.mod & KMOD_CTRL) ksym |= KEYSYM_MOD_CTRL; if (event.key.keysym.mod & KMOD_ALT) ksym |= KEYSYM_MOD_ALT; if (event.key.keysym.mod & KMOD_META) ksym |= KEYSYM_MOD_META; manage_combos(megad, true, RCBK, ksym); if (calibrating) { manage_calibration(RCBK, ksym); break; } switch (events) { int ret; case STARTED: break; case PROMPT: case STOPPED_PROMPT: ret = handle_prompt(ksym, ksym_uni, megad); if (ret & PROMPT_RET_ERROR) { restart_events(megad); return 0; } if (ret & PROMPT_RET_EXIT) { if (events == STOPPED_PROMPT) { // Return to stopped mode. pd_message(stopped_str); events = STOPPED; goto next_event; } if ((ret & PROMPT_RET_MSG) == 0) pd_message("RUNNING."); restart_events(megad); goto next_event; } if (ret & PROMPT_RET_ENTER) { // Back to the prompt only in stopped mode. if (events == STOPPED_PROMPT) goto next_event; if ((ret & PROMPT_RET_MSG) == 0) pd_message(""); restart_events(megad); goto next_event; } // PROMPT_RET_CONT goto next_event; case GAME_GENIE: case STOPPED_GAME_GENIE: if (manage_game_genie(megad, ksym, ksym_uni) == 0) goto next_event; if (events == STOPPED_GAME_GENIE) { // Return to stopped mode. pd_message(stopped_str); events = STOPPED; } else restart_events(megad); goto next_event; case STOPPED: // In basic stopped mode, handle a few keysyms. if (ksym == dgen_game_genie[0]) { pd_message_cursor(strlen(game_genie_str), game_genie_str); events = STOPPED_GAME_GENIE; } else if (ksym == dgen_prompt[0]) { pd_message_cursor(strlen(prompt_str), prompt_str); events = STOPPED_PROMPT; } else if (ksym == dgen_quit[0]) { restart_events(megad); return 0; } else if (ksym == dgen_stop[0]) { pd_message("RUNNING."); restart_events(megad); } #ifdef WITH_DEBUGGER else if (ksym == dgen_debug_enter[0]) ctl_dgen_debug_enter(*(struct ctl *)0, megad); #endif default: goto next_event; } for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) { if (ksym != (*ctl->rc)[RCBK]) continue; assert(ctl->press != NULL); ctl->pressed = true; ctl->coord = false; if (ctl->press(*ctl, megad) == 0) return 0; } if (manage_bindings(megad, true, RCBK, ksym) == 0) return 0; break; case SDL_KEYUP: ksym = event.key.keysym.sym; ksym_uni = kpress[(ksym & 0xff)]; if ((ksym_uni < 0x20) || ((ksym >= SDLK_KP0) && (ksym <= SDLK_KP_EQUALS))) ksym_uni = 0; kpress[(ksym & 0xff)] = 0; if (ksym_uni) ksym = ksym_uni; manage_combos(megad, false, RCBK, ksym); manage_combos(megad, false, RCBK, (ksym | KEYSYM_MOD_ALT)); manage_combos(megad, false, RCBK, (ksym | KEYSYM_MOD_SHIFT)); manage_combos(megad, false, RCBK, (ksym | KEYSYM_MOD_CTRL)); manage_combos(megad, false, RCBK, (ksym | KEYSYM_MOD_META)); if (calibrating) break; if (events != STARTED) break; // The only time we care about key releases is for the // controls, but ignore key modifiers so they never get stuck. for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) { if (ksym != ((*ctl->rc)[RCBK] & ~KEYSYM_MOD_MASK)) continue; ctl->pressed = false; ctl->coord = false; if ((ctl->release != NULL) && (ctl->release(*ctl, megad) == 0)) return 0; } if (manage_bindings(megad, false, RCBK, ksym) == 0) return 0; break; case SDL_MOUSEMOTION: if (!mouse_is_grabbed()) { // Only show mouse pointer for a few seconds. SDL_ShowCursor(1); hide_mouse_when = (pd_usecs() + MOUSE_SHOW_USECS); hide_mouse = true; break; } mouse_motion: which = event.motion.which; pi = 0; ri = 0; if (event.motion.xrel < 0) { plist[pi++] = MO_MOTION(which, 'l'); rlist[ri++] = MO_MOTION(which, 'r'); } else if (event.motion.xrel > 0) { plist[pi++] = MO_MOTION(which, 'r'); rlist[ri++] = MO_MOTION(which, 'l'); } else { rlist[ri++] = MO_MOTION(which, 'r'); rlist[ri++] = MO_MOTION(which, 'l'); } if (event.motion.yrel < 0) { plist[pi++] = MO_MOTION(which, 'u'); rlist[ri++] = MO_MOTION(which, 'd'); } else if (event.motion.yrel > 0) { plist[pi++] = MO_MOTION(which, 'd'); rlist[ri++] = MO_MOTION(which, 'u'); } else { rlist[ri++] = MO_MOTION(which, 'd'); rlist[ri++] = MO_MOTION(which, 'u'); } if (pi) mouse_motion_delay_release(which, true); else mouse_motion_delay_release(which, false); for (i = 0; (i != ri); ++i) manage_combos(megad, false, RCBM, rlist[i]); for (i = 0; (i != pi); ++i) manage_combos(megad, true, RCBM, plist[i]); if (calibrating) { for (i = 0; ((calibrating) && (i != pi)); ++i) manage_calibration(RCBM, plist[i]); break; } if (events != STARTED) break; for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) { // Release buttons first. for (i = 0; (i != ri); ++i) { if ((ctl->pressed == false) || ((uint32_t)(*ctl->rc)[RCBM] != rlist[i])) continue; ctl->pressed = false; ctl->coord = true; ctl->x = event.motion.x; ctl->y = event.motion.y; if ((ctl->release != NULL) && (ctl->release(*ctl, megad) == 0)) return 0; } for (i = 0; (i != pi); ++i) { if ((uint32_t)(*ctl->rc)[RCBM] == plist[i]) { assert(ctl->press != NULL); ctl->pressed = true; ctl->coord = true; ctl->x = event.motion.x; ctl->y = event.motion.y; if (ctl->press(*ctl, megad) == 0) return 0; } } } for (i = 0; (i != ri); ++i) if (!manage_bindings(megad, false, RCBM, rlist[i])) return 0; for (i = 0; (i != pi); ++i) if (!manage_bindings(megad, true, RCBM, plist[i])) return 0; break; case SDL_MOUSEBUTTONDOWN: assert(event.button.state == SDL_PRESSED); #ifdef WITH_DEBUGGER if (!debug_trap) #endif mouse_grab(true); pressed = true; goto mouse_button; case SDL_MOUSEBUTTONUP: assert(event.button.state == SDL_RELEASED); pressed = false; mouse_button: mouse = MO_BUTTON(event.button.which, event.button.button); manage_combos(megad, pressed, RCBM, mouse); if (calibrating) { if (pressed) manage_calibration(RCBM, mouse); break; } if (events != STARTED) break; for (struct ctl* ctl = control; (ctl->rc != NULL); ++ctl) { if ((*ctl->rc)[RCBM] != mouse) continue; ctl->pressed = pressed; ctl->coord = true; ctl->x = event.button.x; ctl->y = event.button.y; if (pressed == false) { if ((ctl->release != NULL) && (ctl->release(*ctl, megad) == 0)) return 0; } else { assert(ctl->press != NULL); if (ctl->press(*ctl, megad) == 0) return 0; } } if (manage_bindings(megad, pressed, RCBM, mouse) == 0) return 0; break; case SDL_VIDEORESIZE: switch (screen_init(event.resize.w, event.resize.h)) { case 0: pd_message("Video resized to %ux%u.", screen.surface->w, screen.surface->h); break; case -1: pd_message("Failed to resize video to %ux%u.", event.resize.w, event.resize.h); break; default: fprintf(stderr, "sdl: fatal error while trying to change screen" " resolution.\n"); return 0; } break; case SDL_QUIT: // We've been politely asked to exit, so let's leave return 0; default: break; } goto next_event; } static size_t pd_message_write(const char *msg, size_t len, unsigned int mark) { uint8_t *buf = (screen.buf.u8 + (screen.pitch * (screen.height - screen.info_height))); size_t ret = 0; screen_lock(); // Clear text area. memset(buf, 0x00, (screen.pitch * screen.info_height)); // Write message. if (len != 0) ret = font_text(buf, screen.width, screen.info_height, screen.Bpp, screen.pitch, msg, len, mark, FONT_TYPE_AUTO); screen_unlock(); return ret; } static size_t pd_message_display(const char *msg, size_t len, unsigned int mark, bool update) { size_t ret = pd_message_write(msg, len, mark); if (update) screen_update(); if (len == 0) info.displayed = 0; else { info.displayed = 1; info.since = pd_usecs(); } return ret; } /** * Process status bar message. */ static void pd_message_process(void) { size_t len = info.length; size_t n; size_t r; if (len == 0) { pd_clear_message(); return; } for (n = 0; (n < len); ++n) if (info.message[n] == '\n') { len = (n + 1); break; } r = pd_message_display(info.message, n, ~0u, false); if (r < n) len = r; memmove(info.message, &(info.message[len]), (info.length - len)); info.length -= len; } /** * Postpone a message. */ static void pd_message_postpone(const char *msg) { strncpy(&info.message[info.length], msg, (sizeof(info.message) - info.length)); info.length = strlen(info.message); info.displayed = 1; } /** * Write a message to the status bar. */ void pd_message(const char *msg, ...) { va_list vl; va_start(vl, msg); vsnprintf(info.message, sizeof(info.message), msg, vl); va_end(vl); info.length = strlen(info.message); pd_message_process(); } void pd_clear_message() { pd_message_display(NULL, 0, ~0u, false); } void pd_show_carthead(md& megad) { struct { const char *p; const char *s; size_t len; } data[] = { #define CE(i, s) { i, s, sizeof(s) } CE("System", megad.cart_head.system_name), CE("Copyright", megad.cart_head.copyright), CE("Domestic name", megad.cart_head.domestic_name), CE("Overseas name", megad.cart_head.overseas_name), CE("Product number", megad.cart_head.product_no), CE("Memo", megad.cart_head.memo), CE("Countries", megad.cart_head.countries) }; size_t i; pd_message_postpone("\n"); for (i = 0; (i < (sizeof(data) / sizeof(data[0]))); ++i) { char buf[256]; size_t j, k; k = (size_t)snprintf(buf, sizeof(buf), "%s: ", data[i].p); if (k >= (sizeof(buf) - 1)) continue; // Filter out extra spaces. for (j = 0; (j < data[i].len); ++j) if (isgraph(data[i].s[j])) break; if (j == data[i].len) continue; while ((j < data[i].len) && (k < (sizeof(buf) - 2))) { if (isgraph(data[i].s[j])) { buf[(k++)] = data[i].s[j]; ++j; continue; } buf[(k++)] = ' '; while ((j < data[i].len) && (!isgraph(data[i].s[j]))) ++j; } if (buf[(k - 1)] == ' ') --k; buf[k] = '\n'; buf[(k + 1)] = '\0'; pd_message_postpone(buf); } } /* Clean up this awful mess :) */ void pd_quit() { size_t i; #ifdef WITH_THREADS screen_update_thread_stop(); #endif if (mdscr.data) { free((void*)mdscr.data); mdscr.data = NULL; } SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_AUDIO); pd_sound_deinit(); if (mdpal) mdpal = NULL; #ifdef WITH_OPENGL release_texture(screen.texture); #endif free(filters_stack_data_buf[0].u8); free(filters_stack_data_buf[1].u8); assert(filters_stack_size <= elemof(filters_stack)); assert(filters_stack_data[0].data == NULL); filters_stack_default = false; for (i = 0; (i != filters_stack_size); ++i) { free(filters_stack_data[i + 1].data); filters_stack_data[i + 1].data = NULL; } filters_stack_size = 0; SDL_Quit(); }