diff --git a/Makefile b/Makefile index 1c42120fc7..a7b9aaa91b 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ include config.mk -TARGET = ssnes +TARGET = ssnes tools/ssnes-joyconfig OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o +JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o +HEADERS = $(wildcard */*.h) $(wildcard *.h) LIBS = DEFINES = -DHAVE_CONFIG_H @@ -74,6 +76,10 @@ else LIBS += $(libsnes) endif +ifneq ($(V),1) + Q := @ +endif + CFLAGS = -Wall -O3 -g -std=gnu99 -I. all: $(TARGET) config.mk @@ -83,17 +89,24 @@ config.mk: configure qb/* @exit 1 ssnes: $(OBJ) - $(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) + $(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) + @$(if $(Q), $(shell echo echo LD $@),) -%.o: %.c config.h config.mk - $(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< +tools/ssnes-joyconfig: $(JOYCONFIG_OBJ) + $(Q)$(CC) -o $@ $(JOYCONFIG_OBJ) $(SDL_LIBS) $(LDFLAGS) + @$(if $(Q), $(shell echo echo LD $@),) + +%.o: %.c config.h config.mk $(HEADERS) + $(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< + @$(if $(Q), $(shell echo echo CC $<),) install: $(TARGET) install -m755 $(TARGET) $(DESTDIR)$(PREFIX)/bin install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg -uninstall: $(TARGET) - rm -rf $(DESTDIR)/$(PREFIX)/bin/$(TARGET) +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/{ssnes,ssnes-joyconfig} + rm -f $(DESTDIR)/etc/ssnes.cfg clean: rm -f *.o @@ -103,6 +116,8 @@ clean: rm -f record/*.o rm -f hqflt/*.o rm -f hqflt/snes_ntsc/*.o + rm -f input/*.o + rm -f tools/*.o rm -f $(TARGET) .PHONY: all install uninstall clean diff --git a/Makefile.win32 b/Makefile.win32 index d66d1cc925..348b99859f 100644 --- a/Makefile.win32 +++ b/Makefile.win32 @@ -1,5 +1,6 @@ TARGET = ssnes.exe OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o +JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o CC = gcc CXX = g++ @@ -11,10 +12,10 @@ libsnes ?= -lsnes LIBS = DEFINES = -I. -LDFLAGS = -L. -static-libgcc +LDFLAGS = -L. -static-libgcc -s SRC_LIBS = -lsamplerate-0 -SDL_LIBS = -lSDLmain -lSDL +SDL_LIBS = -lSDL SDL_CFLAGS = -ISDL ifeq ($(HAVE_SRC), 1) @@ -34,17 +35,27 @@ ifeq ($(HAVE_XML), 1) LIBS += -lxml2 endif +ifneq ($(V),1) + Q := @ +endif + LIBS += $(libsnes) -CFLAGS = -Wall -O3 -g -std=gnu99 -I. +CFLAGS = -Wall -O3 -std=gnu99 -I. -all: $(TARGET) +all: $(TARGET) ssnes-joyconfig.exe $(TARGET): $(OBJ) - $(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) + $(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) + @$(if $(Q), $(shell echo echo LD $@),) %.o: %.c - $(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< + $(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< + @$(if $(Q), $(shell echo echo CC $<),) + +ssnes-joyconfig.exe: $(JOBJ) + $(Q)$(CC) -o ssnes-joyconfig.exe $(JOBJ) $(SDL_LIBS) $(LDFLAGS) + @$(if $(Q), $(shell echo echo LD $@),) clean: rm -f *.o @@ -53,7 +64,13 @@ clean: rm -f gfx/*.o rm -f record/*.o rm -f hqflt/*.o + rm -f input/*.o rm -f hqflt/snes_ntsc/*.o rm -f $(TARGET) + rm -f ssnes-joyconfig.exe + rm -f tools/*.o -.PHONY: all install uninstall clean +dist: all + zip -r ssnes-win32.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll libsamplerate-0.dll ssnes-joyconfig.exe + +.PHONY: all install uninstall clean dist diff --git a/audio/jack.c b/audio/jack.c index 01ae1c9ef6..4e1edae821 100644 --- a/audio/jack.c +++ b/audio/jack.c @@ -29,8 +29,7 @@ #include #include -#define FRAMES(x) (x / (sizeof(int16_t) * 2)) -#define SAMPLES(x) (x / sizeof(int16_t)) +#define FRAMES(x) (x / (sizeof(float) * 2)) typedef struct jack { @@ -61,13 +60,6 @@ static int process_cb(jack_nframes_t nframes, void *data) if (min_avail > nframes) min_avail = nframes; - //static int underrun = 0; - //if (min_avail < nframes) - //{ - // SSNES_LOG("JACK: Underrun count: %d\n", underrun++); - // fprintf(stderr, "required %d frames, got %d.\n", (int)nframes, (int)min_avail); - //} - for (int i = 0; i < 2; i++) { jack_default_audio_sample_t *out = jack_port_get_buffer(jd->ports[i], nframes); @@ -90,25 +82,38 @@ static void shutdown_cb(void *data) pthread_cond_signal(&jd->cond); } -static inline void s16_to_float(jack_default_audio_sample_t * restrict out, const int16_t * restrict in, size_t samples) -{ - for (int i = 0; i < samples; i++) - out[i] = (float)in[i] / 0x8000; -} - -static void parse_ports(const char **dest_ports, const char **jports) +static int parse_ports(char **dest_ports, const char **jports) { int parsed = 0; const char *con = strtok(g_settings.audio.device, ","); if (con) - dest_ports[parsed++] = con; + dest_ports[parsed++] = strdup(con); con = strtok(NULL, ","); if (con) - dest_ports[parsed++] = con; + dest_ports[parsed++] = strdup(con); for (int i = parsed; i < 2; i++) - dest_ports[i] = jports[i]; + dest_ports[i] = strdup(jports[i]); + + return 2; +} + +static size_t find_buffersize(jack_t *jd, int latency) +{ + int frames = latency * g_settings.audio.out_rate / 1000; + + int jack_latency = jack_port_get_total_latency(jd->client, jd->ports[0]); + SSNES_LOG("JACK: Jack latency is %d frames.\n", jack_latency); + + int buffer_frames = frames - jack_latency; + int min_buffer_frames = jack_get_buffer_size(jd->client) * 2; + SSNES_LOG("JACK: Minimum buffer size is %d frames.\n", min_buffer_frames); + + if (buffer_frames < min_buffer_frames) + buffer_frames = min_buffer_frames; + + return buffer_frames * sizeof(jack_default_audio_sample_t); } static void* __jack_init(const char* device, int rate, int latency) @@ -136,24 +141,8 @@ static void* __jack_init(const char* device, int rate, int latency) goto error; } - jack_nframes_t bufsize; - jack_nframes_t jack_bufsize = jack_get_buffer_size(jd->client); - bufsize = (latency * g_settings.audio.out_rate / 1000) > jack_bufsize * 2 ? (latency * g_settings.audio.out_rate / 1000) : jack_bufsize * 2; - bufsize *= sizeof(jack_default_audio_sample_t); - - //fprintf(stderr, "jack buffer size: %d\n", (int)bufsize); - for (int i = 0; i < 2; i++) - { - jd->buffer[i] = jack_ringbuffer_create(bufsize); - if (jd->buffer[i] == NULL) - { - SSNES_ERR("Failed to create buffers.\n"); - goto error; - } - } - - const char *dest_ports[2]; + char *dest_ports[2]; jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput); if (jports == NULL) { @@ -161,7 +150,7 @@ static void* __jack_init(const char* device, int rate, int latency) goto error; } - parse_ports(dest_ports, jports); + int parsed = parse_ports(dest_ports, jports); if (jack_activate(jd->client) < 0) { @@ -178,10 +167,25 @@ static void* __jack_init(const char* device, int rate, int latency) } } + for (int i = 0; i < parsed; i++) + free(dest_ports[i]); + + size_t bufsize = find_buffersize(jd, latency); + + SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t))); + for (int i = 0; i < 2; i++) + { + jd->buffer[i] = jack_ringbuffer_create(bufsize); + if (jd->buffer[i] == NULL) + { + SSNES_ERR("Failed to create buffers.\n"); + goto error; + } + } + pthread_cond_init(&jd->cond, NULL); pthread_mutex_init(&jd->cond_lock, NULL); - jack_free(jports); return jd; @@ -191,19 +195,18 @@ error: return NULL; } -static size_t write_buffer(jack_t *jd, const void *buf, size_t size) +static size_t write_buffer(jack_t *jd, const float *buf, size_t size) { - //fprintf(stderr, "write_buffer: size: %zu\n", size); - // Convert our data to float, deinterleave and write. - jack_default_audio_sample_t out_buffer[size / sizeof(int16_t)]; jack_default_audio_sample_t out_deinterleaved_buffer[2][FRAMES(size)]; - s16_to_float(out_buffer, buf, SAMPLES(size)); for (int i = 0; i < 2; i++) for (size_t j = 0; j < FRAMES(size); j++) - out_deinterleaved_buffer[i][j] = out_buffer[j * 2 + i]; + out_deinterleaved_buffer[i][j] = buf[j * 2 + i]; - for(;;) + size_t frames = FRAMES(size); + + size_t written = 0; + while (written < frames) { if (jd->shutdown) return 0; @@ -212,29 +215,28 @@ static size_t write_buffer(jack_t *jd, const void *buf, size_t size) avail[0] = jack_ringbuffer_write_space(jd->buffer[0]); avail[1] = jack_ringbuffer_write_space(jd->buffer[1]); size_t min_avail = avail[0] < avail[1] ? avail[0] : avail[1]; + min_avail /= sizeof(float); - if (jd->nonblock) + size_t write_frames = frames - written > min_avail ? min_avail : frames - written; + + if (write_frames > 0) { - if (min_avail < FRAMES(size) * sizeof(jack_default_audio_sample_t)) - size = min_avail * 2 * sizeof(int16_t) / sizeof(jack_default_audio_sample_t); - break; + for (int i = 0; i < 2; i++) + jack_ringbuffer_write(jd->buffer[i], (const char*)&out_deinterleaved_buffer[i][written], write_frames * sizeof(jack_default_audio_sample_t)); + written += write_frames; } - else { - //fprintf(stderr, "Write avail is: %d\n", (int)min_avail); - if (min_avail >= FRAMES(size) * sizeof(jack_default_audio_sample_t)) - break; + pthread_mutex_lock(&jd->cond_lock); + pthread_cond_wait(&jd->cond, &jd->cond_lock); + pthread_mutex_unlock(&jd->cond_lock); } - pthread_mutex_lock(&jd->cond_lock); - pthread_cond_wait(&jd->cond, &jd->cond_lock); - pthread_mutex_unlock(&jd->cond_lock); + if (jd->nonblock) + break; } - for (int i = 0; i < 2; i++) - jack_ringbuffer_write(jd->buffer[i], (const char*)out_deinterleaved_buffer[i], FRAMES(size) * sizeof(jack_default_audio_sample_t)); - return size; + return written * sizeof(float) * 2; } static ssize_t __jack_write(void* data, const void* buf, size_t size) @@ -290,6 +292,7 @@ const audio_driver_t audio_jack = { .start = __jack_start, .set_nonblock_state = __jack_set_nonblock_state, .free = __jack_free, + .float_samples = true, .ident = "jack" }; diff --git a/conf/config_file.c b/conf/config_file.c index dc945c2e6a..41e07187fe 100644 --- a/conf/config_file.c +++ b/conf/config_file.c @@ -21,7 +21,6 @@ #include #include #include -#include "general.h" struct entry_list { @@ -127,26 +126,23 @@ static bool parse_line(struct entry_list *list, char *line) return true; } -static void print_config(config_file_t *conf) -{ - struct entry_list *tmp = conf->entries; - while (tmp != NULL) - { - SSNES_LOG("Config => Key: \"%s\", Value: \"%s\"\n", tmp->key, tmp->value); - tmp = tmp->next; - } -} - config_file_t *config_file_new(const char *path) { - FILE *file = fopen(path, "r"); - if (!file) - return NULL; struct config_file *conf = calloc(1, sizeof(*conf)); if (conf == NULL) return NULL; + if (path == NULL) + return conf; + + FILE *file = fopen(path, "r"); + if (!file) + { + free(conf); + return NULL; + } + struct entry_list *tail = conf->entries; while (!feof(file)) @@ -174,9 +170,6 @@ config_file_t *config_file_new(const char *path) } fclose(file); - if (g_extern.verbose) - print_config(conf); - return conf; } @@ -290,4 +283,86 @@ bool config_get_bool(config_file_t *conf, const char *key, bool *in) return false; } +void config_set_string(config_file_t *conf, const char *key, const char *val) +{ + struct entry_list *list = conf->entries; + struct entry_list *last = list; + while (list != NULL) + { + if (strcmp(key, list->key) == 0) + { + free(list->value); + list->value = strdup(val); + return; + } + last = list; + list = list->next; + } + struct entry_list *elem = calloc(1, sizeof(*elem)); + elem->key = strdup(key); + elem->value = strdup(val); + + if (last) + last->next = elem; + else + conf->entries = elem; +} + +void config_set_double(config_file_t *conf, const char *key, double val) +{ + char buf[128]; + snprintf(buf, sizeof(buf), "%lf", val); + config_set_string(conf, key, buf); +} + +void config_set_int(config_file_t *conf, const char *key, int val) +{ + char buf[128]; + snprintf(buf, sizeof(buf), "%d", val); + config_set_string(conf, key, buf); +} + +void config_set_char(config_file_t *conf, const char *key, char val) +{ + char buf[2]; + snprintf(buf, sizeof(buf), "%c", val); + config_set_string(conf, key, buf); +} + +void config_set_bool(config_file_t *conf, const char *key, bool val) +{ + config_set_string(conf, key, val ? "true" : "false"); +} + +bool config_file_write(config_file_t *conf, const char *path) +{ + FILE *file; + + if (path) + { + file = fopen(path, "w"); + if (!file) + return false; + } + else + file = stdout; + + config_file_dump(conf, file); + + if (path) + fclose(file); + + return true; +} + +void config_file_dump(config_file_t *conf, FILE *file) +{ + struct entry_list *list = conf->entries; + + while (list != NULL) + { + fprintf(file, "%s = \"%s\"\n", list->key, list->value); + list = list->next; + } +} diff --git a/conf/config_file.h b/conf/config_file.h index 1992127564..1f19d6d177 100644 --- a/conf/config_file.h +++ b/conf/config_file.h @@ -21,6 +21,7 @@ #include #include +#include typedef struct config_file config_file_t; @@ -30,7 +31,7 @@ typedef struct config_file config_file_t; // - Format is: key = value. There can be as many spaces as you like in-between. // - Value can be wrapped inside "" for multiword strings. (foo = "hai u") -// Loads a config file. Returns NULL if file doesn't exist. +// Loads a config file. Returns NULL if file doesn't exist. NULL path will create an empty config file. config_file_t *config_file_new(const char *path); // Frees config file. void config_file_free(config_file_t *conf); @@ -48,6 +49,19 @@ bool config_get_string(config_file_t *conf, const char *entry, char **in); // Extracts a boolean from config. Valid boolean true are "true" and "1". Valid false are "false" and "0". Other values will be treated as an error. bool config_get_bool(config_file_t *conf, const char *entry, bool *in); +// Setters. Similiar to the getters. +void config_set_double(config_file_t *conf, const char *entry, double value); +void config_set_int(config_file_t *conf, const char *entry, int val); +void config_set_char(config_file_t *conf, const char *entry, char val); +void config_set_string(config_file_t *conf, const char *entry, const char *val); +void config_set_bool(config_file_t *conf, const char *entry, bool val); + +// Write the current config to a file. +bool config_file_write(config_file_t *conf, const char *path); + +// Dump the current config to an already opened file. Does not close the file. +void config_file_dump(config_file_t *conf, FILE *file); + #endif diff --git a/config.def.h b/config.def.h index 1a0e8881b0..1550fd3f47 100644 --- a/config.def.h +++ b/config.def.h @@ -139,31 +139,29 @@ static const bool audio_sync = true; // How far an axis must be tilted to result in a button press #define AXIS_THRESHOLD 0.5 -#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF) -#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U) -#define AXIS_NONE ((uint32_t)0xFFFFFFFFU) - // To figure out which joypad buttons to use, check jstest or similar. -// Axes are configured using the axis number for the positive (up, right) -// direction and the number's two's-complement (~) for negative directions. -// To use the axis, set the button to -1. +// SDL sometimes reverses the axes for some odd reason, but hey. :D // Player 1 static const struct snes_keybind snes_keybinds_1[] = { // SNES button | keyboard key | js btn | js axis | - { SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) }, - { SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) }, - { SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_POS(1) }, - { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_NEG(1) }, - { SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE }, - { SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE }, - { SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) }, + { SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) }, + { SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_NEG(1) }, + { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_POS(1) }, + { SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE }, + { SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE }, + { SSNES_SAVE_STATE_KEY, SDLK_F2, NO_BTN, AXIS_NONE }, + { SSNES_LOAD_STATE_KEY, SDLK_F4, NO_BTN, AXIS_NONE }, + { SSNES_FULLSCREEN_TOGGLE_KEY, SDLK_f, NO_BTN, AXIS_NONE }, + { SSNES_QUIT_KEY, SDLK_ESCAPE, NO_BTN, AXIS_NONE }, { -1 } }; @@ -178,21 +176,66 @@ static const struct snes_keybind snes_keybinds_2[] = { { SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE }, { SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) }, { SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) }, - { SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_POS(1) }, - { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_NEG(1) }, + { SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) }, + { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) }, { SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE }, { SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE }, { -1 } }; -///// Save state -#define SAVE_STATE_KEY SDLK_F2 -///// Load state -#define LOAD_STATE_KEY SDLK_F4 +// Player 3 +static const struct snes_keybind snes_keybinds_3[] = { + // SNES button | keyboard key | js btn | js axis | + { SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) }, + { SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) }, + { SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) }, + { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) }, + { SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE }, + { -1 } +}; -//// Toggles between fullscreen and windowed mode. -#define TOGGLE_FULLSCREEN SDLK_f +// Player 4 +static const struct snes_keybind snes_keybinds_4[] = { + // SNES button | keyboard key | js btn | js axis | + { SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) }, + { SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) }, + { SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) }, + { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) }, + { SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE }, + { -1 } +}; +// Player 5 +static const struct snes_keybind snes_keybinds_5[] = { + // SNES button | keyboard key | js btn | js axis | + { SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) }, + { SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) }, + { SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) }, + { SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) }, + { SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE }, + { SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE }, + { -1 } +}; #endif diff --git a/driver.c b/driver.c index 67ce93c661..4e78e6439b 100644 --- a/driver.c +++ b/driver.c @@ -21,6 +21,7 @@ #include #include #include "hqflt/filters.h" +#include #ifdef HAVE_CONFIG_H #include "config.h" @@ -128,6 +129,9 @@ void uninit_drivers(void) uninit_audio(); } +#define AUDIO_CHUNK_SIZE_BLOCKING 64 +#define AUDIO_CHUNK_SIZE_NONBLOCKING 2048 // So we don't get complete line-noise when fast-forwarding audio. +#define AUDIO_MAX_RATIO 16 void init_audio(void) { if (!g_settings.audio.enable) @@ -138,17 +142,35 @@ void init_audio(void) find_audio_driver(); + g_extern.audio_data.block_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING; + g_extern.audio_data.nonblock_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING; + driver.audio_data = driver.audio->init(strlen(g_settings.audio.device) ? g_settings.audio.device : NULL, g_settings.audio.out_rate, g_settings.audio.latency); if ( driver.audio_data == NULL ) g_extern.audio_active = false; + if (!g_settings.audio.sync && g_extern.audio_active) + { driver.audio->set_nonblock_state(driver.audio_data, true); + g_extern.audio_data.chunk_size = g_extern.audio_data.nonblock_chunk_size; + } + else + g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size; int err; g_extern.source = src_new(g_settings.audio.src_quality, 2, &err); if (!g_extern.source) g_extern.audio_active = false; + + size_t max_bufsamples = g_extern.audio_data.block_chunk_size > g_extern.audio_data.nonblock_chunk_size ? + g_extern.audio_data.block_chunk_size : g_extern.audio_data.nonblock_chunk_size; + + assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO); + assert((g_extern.audio_data.data = malloc(max_bufsamples * sizeof(float)))); + g_extern.audio_data.data_ptr = 0; + assert((g_extern.audio_data.outsamples = malloc(max_bufsamples * sizeof(float) * AUDIO_MAX_RATIO))); + assert((g_extern.audio_data.conv_outsamples = malloc(max_bufsamples * sizeof(int16_t) * AUDIO_MAX_RATIO))); } void uninit_audio(void) @@ -164,6 +186,10 @@ void uninit_audio(void) if ( g_extern.source ) src_delete(g_extern.source); + + free(g_extern.audio_data.data); g_extern.audio_data.data = NULL; + free(g_extern.audio_data.outsamples); g_extern.audio_data.outsamples = NULL; + free(g_extern.audio_data.conv_outsamples); g_extern.audio_data.conv_outsamples = NULL; } void init_video_input(void) diff --git a/driver.h b/driver.h index ec5f3dcca8..7b8303b1be 100644 --- a/driver.h +++ b/driver.h @@ -25,8 +25,14 @@ #include #include -#define SSNES_FAST_FORWARD_KEY 0x666 // Hurr, durr -void set_fast_forward_button(bool state); +enum +{ + SSNES_FAST_FORWARD_KEY = 0x666, // Hurr, durr + SSNES_LOAD_STATE_KEY, + SSNES_SAVE_STATE_KEY, + SSNES_FULLSCREEN_TOGGLE_KEY, + SSNES_QUIT_KEY, +}; struct snes_keybind { @@ -55,12 +61,30 @@ typedef struct audio_driver bool (*start)(void* data); void (*set_nonblock_state)(void* data, bool toggle); // Should we care about blocking in audio thread? Fast forwarding. void (*free)(void* data); + bool float_samples; // Defines if driver will take standard floating point samples, or int16_t samples. const char *ident; } audio_driver_t; +#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF) +#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U) +#define AXIS_NONE ((uint32_t)0xFFFFFFFFU) + #define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF) #define AXIS_POS_GET(x) (x & 0xFFFF) #define AXIS_NONE ((uint32_t)0xFFFFFFFFU) + +#define NO_BTN 0xFFFF // I hope no joypad will ever have this many buttons ... ;) + +#define HAT_UP_MASK (1 << 15) +#define HAT_DOWN_MASK (1 << 14) +#define HAT_LEFT_MASK (1 << 13) +#define HAT_RIGHT_MASK (1 << 12) +#define HAT_MAP(x, hat) ((x & ((1 << 12) - 1)) | hat) + +#define HAT_MASK (HAT_UP_MASK | HAT_DOWN_MASK | HAT_LEFT_MASK | HAT_RIGHT_MASK) +#define GET_HAT_DIR(x) (x & HAT_MASK) +#define GET_HAT(x) (x & (~HAT_MASK)) + typedef struct input_driver { void* (*init)(void); diff --git a/dynamic.c b/dynamic.c index 54bf5a1177..44e02a1e97 100644 --- a/dynamic.c +++ b/dynamic.c @@ -52,6 +52,23 @@ unsigned (*psnes_library_revision_minor)(void); unsigned (*psnes_library_revision_major)(void); bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned); +bool (*psnes_load_cartridge_super_game_boy)( + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned); +bool (*psnes_load_cartridge_bsx)( + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned); +bool (*psnes_load_cartridge_bsx_slotted)( + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned); +bool (*psnes_load_cartridge_sufami_turbo)( + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned); + +void (*psnes_set_controller_port_device)(bool, unsigned); + +bool (*psnes_get_region)(void); unsigned (*psnes_serialize_size)(void); bool (*psnes_serialize)(uint8_t*, unsigned); @@ -84,7 +101,13 @@ static void load_dynamic(void) SYM(snes_library_revision_minor); SYM(snes_library_revision_major); SYM(snes_run); + SYM(snes_get_region); SYM(snes_load_cartridge_normal); + SYM(snes_load_cartridge_super_game_boy); + SYM(snes_load_cartridge_bsx); + SYM(snes_load_cartridge_bsx_slotted); + SYM(snes_load_cartridge_sufami_turbo); + SYM(snes_set_controller_port_device); SYM(snes_serialize_size); SYM(snes_serialize); SYM(snes_unserialize); @@ -111,7 +134,13 @@ static void set_statics(void) SSYM(snes_library_revision_minor); SSYM(snes_library_revision_major); SSYM(snes_run); + SSYM(snes_get_region); SSYM(snes_load_cartridge_normal); + SSYM(snes_load_cartridge_super_game_boy); + SSYM(snes_load_cartridge_bsx); + SSYM(snes_load_cartridge_bsx_slotted); + SSYM(snes_load_cartridge_sufami_turbo); + SSYM(snes_set_controller_port_device); SSYM(snes_serialize_size); SSYM(snes_serialize); SSYM(snes_unserialize); diff --git a/dynamic.h b/dynamic.h index a804da26bc..8fcaa3ddce 100644 --- a/dynamic.h +++ b/dynamic.h @@ -35,6 +35,24 @@ extern unsigned (*psnes_library_revision_minor)(void); extern unsigned (*psnes_library_revision_major)(void); extern bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned); +extern bool (*psnes_load_cartridge_super_game_boy)( + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned); +extern bool (*psnes_load_cartridge_bsx)( + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned); +extern bool (*psnes_load_cartridge_bsx_slotted)( + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned); +extern bool (*psnes_load_cartridge_sufami_turbo)( + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned, + const char*, const uint8_t*, unsigned); + + +extern void (*psnes_set_controller_port_device)(bool, unsigned); + +extern bool (*psnes_get_region)(void); extern unsigned (*psnes_serialize_size)(void); extern bool (*psnes_serialize)(uint8_t*, unsigned); diff --git a/file.c b/file.c index 262eb4983e..bf06e89d6f 100644 --- a/file.c +++ b/file.c @@ -165,3 +165,261 @@ void save_file(const char* path, int type) if ( data && size > 0 ) write_file(path, data, size); } + +static bool load_sgb_rom(void) +{ + void *rom_buf = NULL; + ssize_t rom_len = 0; + + FILE *extra_rom = NULL; + void *extra_rom_buf = NULL; + ssize_t extra_rom_len = 0; + + if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1) + { + SSNES_ERR("Could not read ROM file.\n"); + goto error; + } + + extra_rom = fopen(g_extern.gb_rom_path, "rb"); + if (!extra_rom) + { + SSNES_ERR("Couldn't open GameBoy ROM!\n"); + goto error; + } + + if ((extra_rom_len = read_file(extra_rom, &extra_rom_buf)) == -1) + { + SSNES_ERR("Cannot read GameBoy rom.\n"); + goto error; + } + + if (!psnes_load_cartridge_super_game_boy( + NULL, rom_buf, rom_len, + NULL, extra_rom_buf, extra_rom_len)) + { + SSNES_ERR("Cannot load SGB/GameBoy rom.\n"); + goto error; + } + + if (g_extern.rom_file) + fclose(g_extern.rom_file); + if (extra_rom) + fclose(extra_rom); + free(rom_buf); + free(extra_rom_buf); + return true; + +error: + if (g_extern.rom_file) + fclose(g_extern.rom_file); + if (extra_rom) + fclose(extra_rom); + free(rom_buf); + free(extra_rom_buf); + return false; +} + +static bool load_bsx_rom(bool slotted) +{ + void *rom_buf = NULL; + ssize_t rom_len = 0; + + FILE *extra_rom = NULL; + void *extra_rom_buf = NULL; + ssize_t extra_rom_len = 0; + + if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1) + { + SSNES_ERR("Could not read ROM file.\n"); + goto error; + } + + extra_rom = fopen(g_extern.bsx_rom_path, "rb"); + if (!extra_rom) + { + SSNES_ERR("Couldn't open BSX game rom!\n"); + goto error; + } + + if ((extra_rom_len = read_file(extra_rom, &extra_rom_buf)) == -1) + { + SSNES_ERR("Cannot read BSX game rom.\n"); + goto error; + } + + if (slotted) + { + if (!psnes_load_cartridge_bsx_slotted( + NULL, rom_buf, rom_len, + NULL, extra_rom_buf, extra_rom_len)) + { + SSNES_ERR("Cannot load BSX slotted rom.\n"); + goto error; + } + + } + else + { + if (!psnes_load_cartridge_bsx( + NULL, rom_buf, rom_len, + NULL, extra_rom_buf, extra_rom_len)) + { + SSNES_ERR("Cannot load BSX rom.\n"); + goto error; + } + } + + if (g_extern.rom_file) + fclose(g_extern.rom_file); + if (extra_rom) + fclose(extra_rom); + free(rom_buf); + free(extra_rom_buf); + return true; + +error: + if (g_extern.rom_file) + fclose(g_extern.rom_file); + if (extra_rom) + fclose(extra_rom); + free(rom_buf); + free(extra_rom_buf); + return false; +} + +static bool load_sufami_rom(void) +{ + void *rom_buf = NULL; + ssize_t rom_len = 0; + + FILE *extra_rom[2] = {NULL}; + void *extra_rom_buf[2] = {NULL}; + ssize_t extra_rom_len[2] = {0}; + + if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1) + { + SSNES_ERR("Could not read ROM file.\n"); + goto error; + } + + const char *roms[2] = { g_extern.sufami_rom_path[0], g_extern.sufami_rom_path[1] }; + + for (int i = 0; i < 2; i++) + { + if (strlen(roms[i]) > 0) + { + extra_rom[i] = fopen(roms[i], "rb"); + if (!extra_rom[i]) + { + SSNES_ERR("Couldn't open BSX game rom!\n"); + goto error; + } + + if ((extra_rom_len[i] = read_file(extra_rom[i], &extra_rom_buf[i])) == -1) + { + SSNES_ERR("Cannot read BSX game rom.\n"); + goto error; + } + } + } + + if (!psnes_load_cartridge_sufami_turbo( + NULL, rom_buf, rom_len, + NULL, extra_rom_buf[0], extra_rom_len[0], + NULL, extra_rom_buf[1], extra_rom_len[1])) + { + SSNES_ERR("Cannot load Sufami Turbo rom.\n"); + goto error; + } + + + if (g_extern.rom_file) + fclose(g_extern.rom_file); + for (int i = 0; i < 2; i++) + { + if (extra_rom[i]) + fclose(extra_rom[i]); + free(extra_rom_buf[i]); + } + free(rom_buf); + return true; + +error: + if (g_extern.rom_file) + fclose(g_extern.rom_file); + for (int i = 0; i < 2; i++) + { + if (extra_rom[i]) + fclose(extra_rom[i]); + free(extra_rom_buf[i]); + } + free(rom_buf); + return false; +} + +static bool load_normal_rom(void) +{ + void *rom_buf = NULL; + ssize_t rom_len = 0; + + if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1) + { + SSNES_ERR("Could not read ROM file.\n"); + return false; + } + + if (g_extern.rom_file != NULL) + fclose(g_extern.rom_file); + + SSNES_LOG("ROM size: %d bytes\n", (int)rom_len); + + if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len)) + { + SSNES_ERR("ROM file is not valid!\n"); + free(rom_buf); + return false; + } + + free(rom_buf); + return true; +} + + +bool init_rom_file(enum ssnes_game_type type) +{ + switch (type) + { + case SSNES_CART_SGB: + if (!load_sgb_rom()) + return false; + break; + + case SSNES_CART_NORMAL: + if (!load_normal_rom()) + return false; + break; + + case SSNES_CART_BSX: + if (!load_bsx_rom(false)) + return false; + break; + + case SSNES_CART_BSX_SLOTTED: + if (!load_bsx_rom(true)) + return false; + break; + + case SSNES_CART_SUFAMI: + if (!load_sufami_rom()) + return false; + break; + + default: + SSNES_ERR("Invalid ROM type!\n"); + return false; + } + + return true; +} + diff --git a/file.h b/file.h index 5d4add33a1..d55e1c377e 100644 --- a/file.h +++ b/file.h @@ -19,10 +19,12 @@ #ifndef __SSNES_FILE_H #define __SSNES_FILE_H +#include #include #include #include #include +#include "general.h" ssize_t read_file(FILE *file, void **buf); @@ -31,4 +33,6 @@ void write_file(const char* path, uint8_t* data, size_t size); void load_save_file(const char* path, int type); void save_file(const char* path, int type); +bool init_rom_file(enum ssnes_game_type type); + #endif diff --git a/general.h b/general.h index ef1bfffbb3..174257b3f5 100644 --- a/general.h +++ b/general.h @@ -33,8 +33,9 @@ #endif -#define MAX_PLAYERS 2 -#define MAX_BINDS 14 +#define MAX_PLAYERS 5 +#define MAX_BINDS 18 // Needs to be increased every time there are new binds added. +#define SSNES_NO_JOYPAD 0xFFFF struct settings { struct @@ -70,16 +71,22 @@ struct settings { char driver[32]; struct snes_keybind binds[MAX_PLAYERS][MAX_BINDS]; - int save_state_key; - int load_state_key; - int toggle_fullscreen_key; - int exit_emulator_key; float axis_threshold; + unsigned joypad_map[MAX_PLAYERS]; } input; char libsnes[256]; }; +enum ssnes_game_type +{ + SSNES_CART_NORMAL = 0, + SSNES_CART_SGB, + SSNES_CART_BSX, + SSNES_CART_BSX_SLOTTED, + SSNES_CART_SUFAMI, +}; + struct global { bool verbose; @@ -87,10 +94,40 @@ struct global bool audio_active; bool video_active; + bool has_mouse[2]; + bool has_scope[2]; + bool has_justifier; + bool has_justifiers; + bool has_multitap; + FILE *rom_file; - char savefile_name_srm[256]; + enum ssnes_game_type game_type; + + char gb_rom_path[256]; + char bsx_rom_path[256]; + char sufami_rom_path[2][256]; + char config_path[256]; + char basename[256]; + char savefile_name_srm[256]; + char savefile_name_rtc[512]; // Make sure that fill_pathname has space. + char savefile_name_psrm[512]; + char savefile_name_asrm[512]; + char savefile_name_bsrm[512]; + char savestate_name[256]; + + struct + { + float *data; + size_t data_ptr; + size_t chunk_size; + size_t nonblock_chunk_size; + size_t block_chunk_size; + + float *outsamples; + int16_t *conv_outsamples; + } audio_data; #ifdef HAVE_FFMPEG ffemu_t *rec; diff --git a/gfx/gl.c b/gfx/gl.c index fd77397af9..7b3bf25a00 100644 --- a/gfx/gl.c +++ b/gfx/gl.c @@ -49,6 +49,8 @@ #include "shader_glsl.h" #endif +#include "gl_common.h" + static const GLfloat vertexes[] = { 0, 0, 0, 0, 1, 0, @@ -64,7 +66,6 @@ static const GLfloat tex_coords[] = { }; static bool keep_aspect = true; -static GLuint gl_width = 0, gl_height = 0; typedef struct gl { bool vsync; @@ -218,19 +219,19 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i if (gl->should_resize) { gl->should_resize = false; - SDL_SetVideoMode(gl->win_width, gl->win_height, 32, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0)); + SDL_SetVideoMode(gl->win_width, gl->win_height, 0, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0)); set_viewport(gl); } glClear(GL_COLOR_BUFFER_BIT); - gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl_width, gl_height); + gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl->vp_width, gl->vp_height); if (width != gl->last_width || height != gl->last_height) // res change. need to clear out texture. { gl->last_width = width; gl->last_height = height; - glPixelStorei(GL_UNPACK_ROW_LENGTH, width); + glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->tex_w); uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint16_t)); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, gl->tex_w, gl->tex_h, GL_BGRA, @@ -302,10 +303,11 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0); + SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); - if (!SDL_SetVideoMode(video->width, video->height, 32, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0))) + if (!SDL_SetVideoMode(video->width, video->height, 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0))) return NULL; - + int attr = 0; SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr); if (attr <= 0 && video->vsync) @@ -315,8 +317,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i if (attr <= 0) SSNES_WARN("GL double buffer has not been enabled!\n"); - // Remove that ugly mouse :D - SDL_ShowCursor(SDL_DISABLE); gl_t *gl = calloc(1, sizeof(gl_t)); if (!gl) @@ -325,6 +325,18 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i gl->win_width = video->width; gl->win_height = video->height; gl->vsync = video->vsync; + set_viewport(gl); + + if (!gl_shader_init()) + { + SSNES_ERR("Shader init failed.\n"); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + free(gl); + return NULL; + } + + // Remove that ugly mouse :D + SDL_ShowCursor(SDL_DISABLE); keep_aspect = video->force_aspect; if ( video->smooth ) @@ -332,8 +344,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i else gl->tex_filter = GL_NEAREST; - set_viewport(gl); - glEnable(GL_TEXTURE_2D); glDisable(GL_DITHER); glDisable(GL_DEPTH_TEST); @@ -347,6 +357,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i glGenTextures(1, &gl->texture); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gl->texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); @@ -371,8 +382,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i gl->last_width = gl->tex_w; gl->last_height = gl->tex_h; - gl_shader_init(); - // Hook up SDL input driver to get SDL_QUIT events and RESIZE. sdl_input_t *sdl_input = input_sdl.init(); if (sdl_input) @@ -386,6 +395,13 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i } else *input = NULL; + + if (!gl_check_error()) + { + SDL_QuitSubSystem(SDL_INIT_VIDEO); + free(gl); + return NULL; + } return gl; } diff --git a/gfx/gl_common.h b/gfx/gl_common.h new file mode 100644 index 0000000000..ac315ea10c --- /dev/null +++ b/gfx/gl_common.h @@ -0,0 +1,59 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#ifndef __GL_COMMON_H +#define __GL_COMMON_H + +#include "general.h" + +static inline bool gl_check_error(void) +{ + int error = glGetError(); + switch (error) + { + case GL_INVALID_ENUM: + SSNES_ERR("GL: Invalid enum.\n"); + break; + case GL_INVALID_VALUE: + SSNES_ERR("GL: Invalid value.\n"); + break; + case GL_INVALID_OPERATION: + SSNES_ERR("GL: Invalid operation.\n"); + break; + case GL_STACK_OVERFLOW: + SSNES_ERR("GL: Stack overflow. (wtf)\n"); + break; + case GL_STACK_UNDERFLOW: + SSNES_ERR("GL: Stack underflow. (:v)\n"); + break; + case GL_OUT_OF_MEMORY: + SSNES_ERR("GL: Out of memory. Harhar.\n"); + break; + case GL_TABLE_TOO_LARGE: + SSNES_ERR("GL: Table too large. Big tables scare you! :(\n"); + break; + case GL_NO_ERROR: + return true; + break; + default: + SSNES_ERR("Non specified error :v\n"); + } + + return false; +} + +#endif diff --git a/gfx/shader_glsl.c b/gfx/shader_glsl.c index a9e93a47e6..5a454c9815 100644 --- a/gfx/shader_glsl.c +++ b/gfx/shader_glsl.c @@ -34,6 +34,8 @@ #define GL_GLEXT_PROTOTYPES #include +#include "gl_common.h" + static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL; static PFNGLUSEPROGRAMPROC pglUseProgram = NULL; static PFNGLCREATESHADERPROC pglCreateShader = NULL; @@ -49,6 +51,8 @@ static PFNGLUNIFORM2FVPROC pglUniform2fv = NULL; static PFNGLUNIFORM4FVPROC pglUniform4fv = NULL; static PFNGLGETSHADERIVPROC pglGetShaderiv = NULL; static PFNGLGETSHADERINFOLOGPROC pglGetShaderInfoLog = NULL; +static PFNGLGETPROGRAMIVPROC pglGetProgramiv = NULL; +static PFNGLGETPROGRAMINFOLOGPROC pglGetProgramInfoLog = NULL; static bool glsl_enable = false; static GLuint gl_program; @@ -154,6 +158,20 @@ static void print_shader_log(GLuint obj) SSNES_LOG("Shader log: %s\n", info_log); } +static void print_linker_log(GLuint obj) +{ + int info_len = 0; + int max_len; + + pglGetProgramiv(obj, GL_INFO_LOG_LENGTH, &max_len); + + char info_log[max_len]; + pglGetProgramInfoLog(obj, max_len, &info_len, info_log); + + if (info_len > 0) + SSNES_LOG("Linker log: %s\n", info_log); +} + bool gl_glsl_init(const char *path) { // Load shader functions. @@ -172,13 +190,15 @@ bool gl_glsl_init(const char *path) pglUniform4fv = SDL_GL_GetProcAddress("glUniform4fv"); pglGetShaderiv = SDL_GL_GetProcAddress("glGetShaderiv"); pglGetShaderInfoLog = SDL_GL_GetProcAddress("glGetShaderInfoLog"); + pglGetProgramiv = SDL_GL_GetProcAddress("glGetProgramiv"); + pglGetProgramInfoLog = SDL_GL_GetProcAddress("glGetProgramInfoLog"); SSNES_LOG("Checking GLSL shader support ...\n"); bool shader_support = pglCreateProgram && pglUseProgram && pglCreateShader && pglDeleteShader && pglShaderSource && pglCompileShader && pglAttachShader && pglDetachShader && pglLinkProgram && pglGetUniformLocation && pglUniform1i && pglUniform2fv && pglUniform4fv - && pglGetShaderiv && pglGetShaderInfoLog; + && pglGetShaderiv && pglGetShaderInfoLog && pglGetProgramiv && pglGetProgramInfoLog; if (!shader_support) { @@ -218,8 +238,15 @@ bool gl_glsl_init(const char *path) { pglLinkProgram(gl_program); pglUseProgram(gl_program); + print_linker_log(gl_program); + + GLint location = pglGetUniformLocation(gl_program, "rubyTexture"); + pglUniform1i(location, 0); } + if (!gl_check_error()) + return false; + glsl_enable = true; return true; } @@ -246,6 +273,7 @@ void gl_glsl_set_params(unsigned width, unsigned height, float textureSize[2] = {tex_width, tex_height}; location = pglGetUniformLocation(gl_program, "rubyTextureSize"); pglUniform2fv(location, 1, textureSize); + } } diff --git a/hqflt/cg/crt.cg b/hqflt/cg/crt.cg index bee618ad38..bb62d1da05 100644 --- a/hqflt/cg/crt.cg +++ b/hqflt/cg/crt.cg @@ -91,7 +91,8 @@ output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXU else mcol.rb = 0.7; - OUT.color = pow(mcol*(col * weights + col2 * weights2), 1.0/2.2); + //OUT.color = pow(mcol*(col * weights + col2 * weights2), 1.0/2.2); + OUT.color = 1.0; return OUT; } diff --git a/hqflt/filters.h b/hqflt/filters.h index 0e73b294f2..d8b766b584 100644 --- a/hqflt/filters.h +++ b/hqflt/filters.h @@ -30,6 +30,7 @@ #include "bleed.h" #include "ntsc.h" +#define FILTER_NONE 0 #define FILTER_HQ2X 1 #define FILTER_HQ4X 2 #define FILTER_GRAYSCALE 3 diff --git a/input/sdl.c b/input/sdl.c index a2260ebe2e..5899fc46dd 100644 --- a/input/sdl.c +++ b/input/sdl.c @@ -34,29 +34,43 @@ static void* sdl_input_init(void) if (SDL_Init(SDL_INIT_JOYSTICK) < 0) return NULL; + SDL_JoystickEventState(SDL_IGNORE); sdl->num_joysticks = SDL_NumJoysticks(); - if (sdl->num_joysticks > 2) - sdl->num_joysticks = 2; - for (unsigned i = 0; i < sdl->num_joysticks; i++) - { - sdl->joysticks[i] = SDL_JoystickOpen(i); - if (!sdl->joysticks[i]) - { - SSNES_ERR("Couldn't open SDL joystick %d\n", i); - free(sdl); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); - return NULL; - } - SSNES_LOG("Opened Joystick: %s\n", SDL_JoystickName(i)); - sdl->num_axes[i] = SDL_JoystickNumAxes(sdl->joysticks[i]); - sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]); + for (unsigned i = 0; i < MAX_PLAYERS; i++) + { + if (g_settings.input.joypad_map[i] == SSNES_NO_JOYPAD) + continue; + + if (sdl->num_joysticks > g_settings.input.joypad_map[i]) + { + sdl->joysticks[i] = SDL_JoystickOpen(g_settings.input.joypad_map[i]); + if (!sdl->joysticks[i]) + { + SSNES_ERR("Couldn't open SDL joystick #%u on SNES port %u\n", g_settings.input.joypad_map[i], i + 1); + free(sdl); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + return NULL; + } + + SSNES_LOG("Opened Joystick: %s #%u on port %u\n", + SDL_JoystickName(g_settings.input.joypad_map[i]), g_settings.input.joypad_map[i], i + 1); + sdl->num_axes[i] = SDL_JoystickNumAxes(sdl->joysticks[i]); + sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]); + sdl->num_hats[i] = SDL_JoystickNumHats(sdl->joysticks[i]); + } + else + { + SSNES_WARN("Desired SDL joystick #%u on port %u, but SDL can only detect %u joysticks ...\n", + g_settings.input.joypad_map[i], i + 1, sdl->num_joysticks); + } } return sdl; } -static bool sdl_key_pressed(void *data, int key) + +static bool sdl_key_pressed(int key) { int num_keys; Uint8 *keymap = SDL_GetKeyState(&num_keys); @@ -67,27 +81,60 @@ static bool sdl_key_pressed(void *data, int key) return keymap[key]; } -static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key) +static bool sdl_joykey_pressed(sdl_input_t *sdl, int port_num, uint16_t joykey) { - if (sdl_key_pressed(sdl, key->key)) - return true; - if (port_num >= sdl->num_joysticks) - return false; - if (key->joykey < sdl->num_buttons[port_num] && SDL_JoystickGetButton(sdl->joysticks[port_num], key->joykey)) - return true; - - if (key->joyaxis != AXIS_NONE) + // Check hat. + if (GET_HAT_DIR(joykey)) { - if (AXIS_NEG_GET(key->joyaxis) < sdl->num_axes[port_num]) + int hat = GET_HAT(joykey); + if (hat < sdl->num_hats[port_num]) { - Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(key->joyaxis)); + Uint8 dir = SDL_JoystickGetHat(sdl->joysticks[port_num], hat); + switch (GET_HAT_DIR(joykey)) + { + case HAT_UP_MASK: + if (dir & SDL_HAT_UP) + return true; + break; + case HAT_DOWN_MASK: + if (dir & SDL_HAT_DOWN) + return true; + break; + case HAT_LEFT_MASK: + if (dir & SDL_HAT_LEFT) + return true; + break; + case HAT_RIGHT_MASK: + if (dir & SDL_HAT_RIGHT) + return true; + break; + default: + break; + } + } + } + else // Check the button + { + if (joykey < sdl->num_buttons[port_num] && SDL_JoystickGetButton(sdl->joysticks[port_num], joykey)) + return true; + } + return false; +} + +static bool sdl_axis_pressed(sdl_input_t *sdl, int port_num, uint32_t joyaxis) +{ + if (joyaxis != AXIS_NONE) + { + if (AXIS_NEG_GET(joyaxis) < sdl->num_axes[port_num]) + { + Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(joyaxis)); float scaled = (float)val / 0x8000; if (scaled < -g_settings.input.axis_threshold) return true; } - if (AXIS_POS_GET(key->joyaxis) < sdl->num_axes[port_num]) + if (AXIS_POS_GET(joyaxis) < sdl->num_axes[port_num]) { - Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(key->joyaxis)); + Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(joyaxis)); float scaled = (float)val / 0x8000; if (scaled > g_settings.input.axis_threshold) return true; @@ -97,45 +144,166 @@ static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_key return false; } -static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id) +static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key) { - sdl_input_t *sdl = data; - if (device != SNES_DEVICE_JOYPAD) - return 0; + if (sdl_key_pressed(key->key)) + return true; + if (sdl->joysticks[port_num] == NULL) + return false; + if (sdl_joykey_pressed(sdl, port_num, key->joykey)) + return true; + if (sdl_axis_pressed(sdl, port_num, key->joyaxis)) + return true; - const struct snes_keybind *snes_keybinds = binds[port == SNES_PORT_1 ? 0 : 1]; + return false; +} + +static bool sdl_bind_button_pressed(void *data, int key) +{ + // Only let player 1 use special binds called from main loop. + const struct snes_keybind *binds = g_settings.input.binds[0]; + for (int i = 0; binds[i].id != -1; i++) + { + if (binds[i].id == key) + return sdl_is_pressed(data, 0, &binds[i]); + } + return false; +} + +static int16_t sdl_joypad_device_state(sdl_input_t *sdl, const struct snes_keybind **binds, + int port_num, unsigned device, unsigned index, unsigned id) +{ + const struct snes_keybind *snes_keybinds = binds[port_num]; - // Checks if button is pressed, and sets fast-forwarding state - bool pressed = false; - int port_num = port == SNES_PORT_1 ? 0 : 1; for (int i = 0; snes_keybinds[i].id != -1; i++) { - if (snes_keybinds[i].id == SSNES_FAST_FORWARD_KEY) - set_fast_forward_button(sdl_is_pressed(sdl, port_num, &snes_keybinds[i])); - else if (!pressed && snes_keybinds[i].id == (int)id) - pressed = sdl_is_pressed(sdl, port_num, &snes_keybinds[i]); + if (snes_keybinds[i].id == (int)id) + return sdl_is_pressed(sdl, port_num, &snes_keybinds[i]); } - return pressed; + return false; +} + +static int16_t sdl_mouse_device_state(sdl_input_t *sdl, bool port, unsigned id) +{ + // Might implement support for joypad mapping later. + (void)port; + switch (id) + { + case SNES_DEVICE_ID_MOUSE_LEFT: + return sdl->mouse_l; + case SNES_DEVICE_ID_MOUSE_RIGHT: + return sdl->mouse_r; + case SNES_DEVICE_ID_MOUSE_X: + return sdl->mouse_x; + case SNES_DEVICE_ID_MOUSE_Y: + return sdl->mouse_y; + default: + return 0; + } +} + +// TODO: Missing some controllers, but hey :) +static int16_t sdl_scope_device_state(sdl_input_t *sdl, unsigned id) +{ + switch (id) + { + case SNES_DEVICE_ID_SUPER_SCOPE_X: + return sdl->mouse_x; + case SNES_DEVICE_ID_SUPER_SCOPE_Y: + return sdl->mouse_y; + case SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER: + return sdl->mouse_l; + case SNES_DEVICE_ID_SUPER_SCOPE_CURSOR: + return sdl->mouse_m; + case SNES_DEVICE_ID_SUPER_SCOPE_TURBO: + return sdl->mouse_r; + default: + return 0; + } +} + +// TODO: Support two players. +static int16_t sdl_justifier_device_state(sdl_input_t *sdl, unsigned index, unsigned id) +{ + if (index == 0) + { + switch (id) + { + case SNES_DEVICE_ID_JUSTIFIER_X: + return sdl->mouse_x; + case SNES_DEVICE_ID_JUSTIFIER_Y: + return sdl->mouse_y; + case SNES_DEVICE_ID_JUSTIFIER_TRIGGER: + return sdl->mouse_l; + case SNES_DEVICE_ID_JUSTIFIER_START: + return sdl->mouse_r; + default: + return 0; + } + } + else + return 0; +} + +static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id) +{ + switch (device) + { + case SNES_DEVICE_JOYPAD: + return sdl_joypad_device_state(data, binds, port == SNES_PORT_1 ? 0 : 1, device, index, id); + case SNES_DEVICE_MULTITAP: + return sdl_joypad_device_state(data, binds, (port == SNES_PORT_2) ? 1 + index : 0, device, index, id); + case SNES_DEVICE_MOUSE: + return sdl_mouse_device_state(data, port, id); + case SNES_DEVICE_SUPER_SCOPE: + return sdl_scope_device_state(data, id); + case SNES_DEVICE_JUSTIFIER: + case SNES_DEVICE_JUSTIFIERS: + return sdl_justifier_device_state(data, index, id); + + default: + return 0; + } } static void sdl_input_free(void *data) { if (data) { + // Flush out all pending events. + SDL_Event event; + while (SDL_PollEvent(&event)); + sdl_input_t *sdl = data; - for (int i = 0; i < sdl->num_joysticks; i++) - SDL_JoystickClose(sdl->joysticks[i]); + for (int i = 0; i < MAX_PLAYERS; i++) + { + if (sdl->joysticks[i]) + SDL_JoystickClose(sdl->joysticks[i]); + } free(data); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } } +static void sdl_poll_mouse(sdl_input_t *sdl) +{ + int _x, _y; + Uint8 btn = SDL_GetRelativeMouseState(&_x, &_y); + sdl->mouse_x = _x; + sdl->mouse_y = _y; + sdl->mouse_l = SDL_BUTTON(SDL_BUTTON_LEFT) & btn ? 1 : 0; + sdl->mouse_r = SDL_BUTTON(SDL_BUTTON_RIGHT) & btn ? 1 : 0; + sdl->mouse_m = SDL_BUTTON(SDL_BUTTON_MIDDLE) & btn ? 1 : 0; +} + static void sdl_input_poll(void *data) { SDL_PumpEvents(); SDL_Event event; + SDL_JoystickUpdate(); + sdl_poll_mouse(data); sdl_input_t *sdl = data; // Search for events... @@ -170,7 +338,7 @@ const input_driver_t input_sdl = { .init = sdl_input_init, .poll = sdl_input_poll, .input_state = sdl_input_state, - .key_pressed = sdl_key_pressed, + .key_pressed = sdl_bind_button_pressed, .free = sdl_input_free, .ident = "sdl" }; diff --git a/input/ssnes_sdl_input.h b/input/ssnes_sdl_input.h index 5833396e3f..7e92749ceb 100644 --- a/input/ssnes_sdl_input.h +++ b/input/ssnes_sdl_input.h @@ -19,11 +19,13 @@ #define __SSNES_SDL_INPUT_H #include "SDL.h" +#include "general.h" typedef struct sdl_input { - SDL_Joystick *joysticks[2]; - unsigned num_axes[2]; - unsigned num_buttons[2]; + SDL_Joystick *joysticks[MAX_PLAYERS]; + unsigned num_axes[MAX_PLAYERS]; + unsigned num_buttons[MAX_PLAYERS]; + unsigned num_hats[MAX_PLAYERS]; unsigned num_joysticks; // A video driver could pre-init with the SDL driver and have it handle resizing events... @@ -31,6 +33,8 @@ typedef struct sdl_input bool *should_resize; unsigned *new_width; unsigned *new_height; + int16_t mouse_x, mouse_y; + int16_t mouse_l, mouse_r, mouse_m; } sdl_input_t; #endif diff --git a/settings.c b/settings.c index 5337f042c7..e44f751225 100644 --- a/settings.c +++ b/settings.c @@ -17,10 +17,10 @@ #include "general.h" #include "conf/config_file.h" -#include "config.def.h" #include #include #include "hqflt/filters.h" +#include "config.def.h" #ifdef HAVE_CONFIG_H #include "config.h" @@ -96,6 +96,7 @@ static void set_defaults(void) g_settings.video.smooth = video_smooth; g_settings.video.force_aspect = force_aspect; g_settings.video.aspect_ratio = SNES_ASPECT_RATIO; + g_settings.video.filter = FILTER_NONE; g_settings.audio.enable = audio_enable; g_settings.audio.out_rate = out_rate; @@ -108,14 +109,20 @@ static void set_defaults(void) assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1)); assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2)); + assert(sizeof(g_settings.input.binds[2]) >= sizeof(snes_keybinds_3)); + assert(sizeof(g_settings.input.binds[3]) >= sizeof(snes_keybinds_4)); + assert(sizeof(g_settings.input.binds[4]) >= sizeof(snes_keybinds_5)); memcpy(g_settings.input.binds[0], snes_keybinds_1, sizeof(snes_keybinds_1)); memcpy(g_settings.input.binds[1], snes_keybinds_2, sizeof(snes_keybinds_2)); + memcpy(g_settings.input.binds[2], snes_keybinds_3, sizeof(snes_keybinds_3)); + memcpy(g_settings.input.binds[3], snes_keybinds_4, sizeof(snes_keybinds_4)); + memcpy(g_settings.input.binds[4], snes_keybinds_5, sizeof(snes_keybinds_5)); - g_settings.input.save_state_key = SAVE_STATE_KEY; - g_settings.input.load_state_key = LOAD_STATE_KEY; - g_settings.input.toggle_fullscreen_key = TOGGLE_FULLSCREEN; g_settings.input.axis_threshold = AXIS_THRESHOLD; - g_settings.input.exit_emulator_key = SDLK_ESCAPE; + for (int i = 0; i < 2; i++) + g_settings.input.joypad_map[i] = i; + for (int i = 2; i < MAX_PLAYERS; i++) + g_settings.input.joypad_map[i] = SSNES_NO_JOYPAD; } void parse_config(void) @@ -164,6 +171,9 @@ void parse_config(void) if (conf == NULL) return; + if (g_extern.verbose) + config_file_dump(conf, stderr); + int tmp_int; double tmp_double; bool tmp_bool; @@ -246,6 +256,22 @@ void parse_config(void) if (config_get_double(conf, "input_axis_threshold", &tmp_double)) g_settings.input.axis_threshold = tmp_double; + // Joypad mapping. + if (config_get_int(conf, "input_player1_joypad_index", &tmp_int)) + g_settings.input.joypad_map[0] = tmp_int; + + if (config_get_int(conf, "input_player2_joypad_index", &tmp_int)) + g_settings.input.joypad_map[1] = tmp_int; + + if (config_get_int(conf, "input_player3_joypad_index", &tmp_int)) + g_settings.input.joypad_map[2] = tmp_int; + + if (config_get_int(conf, "input_player4_joypad_index", &tmp_int)) + g_settings.input.joypad_map[3] = tmp_int; + + if (config_get_int(conf, "input_player5_joypad_index", &tmp_int)) + g_settings.input.joypad_map[4] = tmp_int; + // Audio settings. if (config_get_bool(conf, "audio_enable", &tmp_bool)) g_settings.audio.enable = tmp_bool; @@ -311,38 +337,105 @@ struct bind_map int snes_key; }; + +#define DECLARE_BIND(x, bind) { "input_" #x, "input_" #x "_btn", "input_" #x "_axis", bind }, // Big and nasty bind map... :) -static const struct bind_map bind_maps[2][13] = { +static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = { { - { "input_player1_a", "input_player1_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A }, - { "input_player1_b", "input_player1_b_btn", NULL, SNES_DEVICE_ID_JOYPAD_B }, - { "input_player1_y", "input_player1_y_btn", NULL, SNES_DEVICE_ID_JOYPAD_Y }, - { "input_player1_x", "input_player1_x_btn", NULL, SNES_DEVICE_ID_JOYPAD_X }, - { "input_player1_start", "input_player1_start_btn", NULL, SNES_DEVICE_ID_JOYPAD_START }, - { "input_player1_select", "input_player1_select_btn", NULL, SNES_DEVICE_ID_JOYPAD_SELECT }, - { "input_player1_l", "input_player1_l_btn", NULL, SNES_DEVICE_ID_JOYPAD_L }, - { "input_player1_r", "input_player1_r_btn", NULL, SNES_DEVICE_ID_JOYPAD_R }, - { "input_player1_left", "input_player1_left_btn", "input_player1_left_axis", SNES_DEVICE_ID_JOYPAD_LEFT }, - { "input_player1_right", "input_player1_right_btn", "input_player1_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT }, - { "input_player1_up", "input_player1_up_btn", "input_player1_up_axis", SNES_DEVICE_ID_JOYPAD_UP }, - { "input_player1_down", "input_player1_down_btn", "input_player1_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN }, - { "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SSNES_FAST_FORWARD_KEY } - }, + DECLARE_BIND(player1_a, SNES_DEVICE_ID_JOYPAD_A) + DECLARE_BIND(player1_b, SNES_DEVICE_ID_JOYPAD_B) + DECLARE_BIND(player1_y, SNES_DEVICE_ID_JOYPAD_Y) + DECLARE_BIND(player1_x, SNES_DEVICE_ID_JOYPAD_X) + DECLARE_BIND(player1_start, SNES_DEVICE_ID_JOYPAD_START) + DECLARE_BIND(player1_select, SNES_DEVICE_ID_JOYPAD_SELECT) + DECLARE_BIND(player1_l, SNES_DEVICE_ID_JOYPAD_L) + DECLARE_BIND(player1_r, SNES_DEVICE_ID_JOYPAD_R) + DECLARE_BIND(player1_left, SNES_DEVICE_ID_JOYPAD_LEFT) + DECLARE_BIND(player1_right, SNES_DEVICE_ID_JOYPAD_RIGHT) + DECLARE_BIND(player1_up, SNES_DEVICE_ID_JOYPAD_UP) + DECLARE_BIND(player1_down, SNES_DEVICE_ID_JOYPAD_DOWN) + DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY) + DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY) + DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY) + DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY) + DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY) + }, { - { "input_player2_a", "input_player2_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A }, - { "input_player2_b", "input_player2_b_btn", NULL, SNES_DEVICE_ID_JOYPAD_B }, - { "input_player2_y", "input_player2_y_btn", NULL, SNES_DEVICE_ID_JOYPAD_Y }, - { "input_player2_x", "input_player2_x_btn", NULL, SNES_DEVICE_ID_JOYPAD_X }, - { "input_player2_start", "input_player2_start_btn", NULL, SNES_DEVICE_ID_JOYPAD_START }, - { "input_player2_select", "input_player2_select_btn", NULL, SNES_DEVICE_ID_JOYPAD_SELECT }, - { "input_player2_l", "input_player2_l_btn", NULL, SNES_DEVICE_ID_JOYPAD_L }, - { "input_player2_r", "input_player2_r_btn", NULL, SNES_DEVICE_ID_JOYPAD_R }, - { "input_player2_left", "input_player2_left_btn", "input_player2_left_axis", SNES_DEVICE_ID_JOYPAD_LEFT }, - { "input_player2_right", "input_player2_right_btn", "input_player2_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT }, - { "input_player2_up", "input_player2_up_btn", "input_player2_up_axis", SNES_DEVICE_ID_JOYPAD_UP }, - { "input_player2_down", "input_player2_down_btn", "input_player2_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN }, - { "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SSNES_FAST_FORWARD_KEY } - } + DECLARE_BIND(player2_a, SNES_DEVICE_ID_JOYPAD_A) + DECLARE_BIND(player2_b, SNES_DEVICE_ID_JOYPAD_B) + DECLARE_BIND(player2_y, SNES_DEVICE_ID_JOYPAD_Y) + DECLARE_BIND(player2_x, SNES_DEVICE_ID_JOYPAD_X) + DECLARE_BIND(player2_start, SNES_DEVICE_ID_JOYPAD_START) + DECLARE_BIND(player2_select, SNES_DEVICE_ID_JOYPAD_SELECT) + DECLARE_BIND(player2_l, SNES_DEVICE_ID_JOYPAD_L) + DECLARE_BIND(player2_r, SNES_DEVICE_ID_JOYPAD_R) + DECLARE_BIND(player2_left, SNES_DEVICE_ID_JOYPAD_LEFT) + DECLARE_BIND(player2_right, SNES_DEVICE_ID_JOYPAD_RIGHT) + DECLARE_BIND(player2_up, SNES_DEVICE_ID_JOYPAD_UP) + DECLARE_BIND(player2_down, SNES_DEVICE_ID_JOYPAD_DOWN) + DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY) + DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY) + DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY) + DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY) + DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY) + }, + { + DECLARE_BIND(player3_a, SNES_DEVICE_ID_JOYPAD_A) + DECLARE_BIND(player3_b, SNES_DEVICE_ID_JOYPAD_B) + DECLARE_BIND(player3_y, SNES_DEVICE_ID_JOYPAD_Y) + DECLARE_BIND(player3_x, SNES_DEVICE_ID_JOYPAD_X) + DECLARE_BIND(player3_start, SNES_DEVICE_ID_JOYPAD_START) + DECLARE_BIND(player3_select, SNES_DEVICE_ID_JOYPAD_SELECT) + DECLARE_BIND(player3_l, SNES_DEVICE_ID_JOYPAD_L) + DECLARE_BIND(player3_r, SNES_DEVICE_ID_JOYPAD_R) + DECLARE_BIND(player3_left, SNES_DEVICE_ID_JOYPAD_LEFT) + DECLARE_BIND(player3_right, SNES_DEVICE_ID_JOYPAD_RIGHT) + DECLARE_BIND(player3_up, SNES_DEVICE_ID_JOYPAD_UP) + DECLARE_BIND(player3_down, SNES_DEVICE_ID_JOYPAD_DOWN) + DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY) + DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY) + DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY) + DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY) + DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY) + }, + { + DECLARE_BIND(player4_a, SNES_DEVICE_ID_JOYPAD_A) + DECLARE_BIND(player4_b, SNES_DEVICE_ID_JOYPAD_B) + DECLARE_BIND(player4_y, SNES_DEVICE_ID_JOYPAD_Y) + DECLARE_BIND(player4_x, SNES_DEVICE_ID_JOYPAD_X) + DECLARE_BIND(player4_start, SNES_DEVICE_ID_JOYPAD_START) + DECLARE_BIND(player4_select, SNES_DEVICE_ID_JOYPAD_SELECT) + DECLARE_BIND(player4_l, SNES_DEVICE_ID_JOYPAD_L) + DECLARE_BIND(player4_r, SNES_DEVICE_ID_JOYPAD_R) + DECLARE_BIND(player4_left, SNES_DEVICE_ID_JOYPAD_LEFT) + DECLARE_BIND(player4_right, SNES_DEVICE_ID_JOYPAD_RIGHT) + DECLARE_BIND(player4_up, SNES_DEVICE_ID_JOYPAD_UP) + DECLARE_BIND(player4_down, SNES_DEVICE_ID_JOYPAD_DOWN) + DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY) + DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY) + DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY) + DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY) + DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY) + }, + { + DECLARE_BIND(player5_a, SNES_DEVICE_ID_JOYPAD_A) + DECLARE_BIND(player5_b, SNES_DEVICE_ID_JOYPAD_B) + DECLARE_BIND(player5_y, SNES_DEVICE_ID_JOYPAD_Y) + DECLARE_BIND(player5_x, SNES_DEVICE_ID_JOYPAD_X) + DECLARE_BIND(player5_start, SNES_DEVICE_ID_JOYPAD_START) + DECLARE_BIND(player5_select, SNES_DEVICE_ID_JOYPAD_SELECT) + DECLARE_BIND(player5_l, SNES_DEVICE_ID_JOYPAD_L) + DECLARE_BIND(player5_r, SNES_DEVICE_ID_JOYPAD_R) + DECLARE_BIND(player5_left, SNES_DEVICE_ID_JOYPAD_LEFT) + DECLARE_BIND(player5_right, SNES_DEVICE_ID_JOYPAD_RIGHT) + DECLARE_BIND(player5_up, SNES_DEVICE_ID_JOYPAD_UP) + DECLARE_BIND(player5_down, SNES_DEVICE_ID_JOYPAD_DOWN) + DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY) + DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY) + DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY) + DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY) + DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY) + }, }; struct key_map @@ -352,6 +445,7 @@ struct key_map }; // Edit: Not portable to different input systems atm. Might move this map into the driver itself or something. +// However, this should map nicely over to other systems aswell since the definition are mostly the same anyways. static const struct key_map sdlk_map[] = { { "left", SDLK_LEFT }, { "right", SDLK_RIGHT }, @@ -415,12 +509,12 @@ static int find_sdlk_key(const char *str) static void read_keybinds(config_file_t *conf) { char *tmp_key = NULL; - int tmp_btn; + char *tmp_btn = NULL; char *tmp_axis = NULL; - for (int j = 0; j < 1; j++) + for (int j = 0; j < MAX_PLAYERS; j++) { - for (int i = 0; i < sizeof(bind_maps[j])/sizeof(struct bind_map); i++) + for (int i = 0; i < sizeof(bind_maps[0])/sizeof(struct bind_map); i++) { struct snes_keybind *bind = find_snes_bind(j, bind_maps[j][i].snes_key); if (!bind) @@ -437,10 +531,35 @@ static void read_keybinds(config_file_t *conf) tmp_key = NULL; } - if (bind_maps[j][i].btn && config_get_int(conf, bind_maps[j][i].btn, &tmp_btn)) + if (bind_maps[j][i].btn && config_get_string(conf, bind_maps[j][i].btn, &tmp_btn)) { - if (tmp_btn >= 0) - bind->joykey = tmp_btn; + const char *btn = tmp_btn; + if (*btn++ == 'h') + { + if (isdigit(*btn)) + { + char *dir = NULL; + int hat = strtol(btn, &dir, 0); + int hat_dir = 0; + if (dir) + { + if (strcasecmp(dir, "up") == 0) + hat_dir = HAT_UP_MASK; + else if (strcasecmp(dir, "down") == 0) + hat_dir = HAT_DOWN_MASK; + else if (strcasecmp(dir, "left") == 0) + hat_dir = HAT_LEFT_MASK; + else if (strcasecmp(dir, "right") == 0) + hat_dir = HAT_RIGHT_MASK; + + if (hat_dir) + bind->joykey = HAT_MAP(hat, hat_dir); + } + } + } + else + bind->joykey = strtol(tmp_btn, NULL, 0); + free(tmp_btn); } if (bind_maps[j][i].axis && config_get_string(conf, bind_maps[j][i].axis, &tmp_axis)) @@ -459,38 +578,5 @@ static void read_keybinds(config_file_t *conf) } } } - - char *tmp_str; - if (config_get_string(conf, "input_toggle_fullscreen", &tmp_str)) - { - int key = find_sdlk_key(tmp_str); - if (key >= 0) - g_settings.input.toggle_fullscreen_key = key; - free(tmp_str); - } - if (config_get_string(conf, "input_save_state", &tmp_str)) - { - int key = find_sdlk_key(tmp_str); - if (key >= 0) - g_settings.input.save_state_key = key; - free(tmp_str); - } - if (config_get_string(conf, "input_load_state", &tmp_str)) - { - int key = find_sdlk_key(tmp_str); - if (key >= 0) - g_settings.input.load_state_key = key; - free(tmp_str); - } - if (config_get_string(conf, "input_exit_emulator", &tmp_str)) - { - int key = find_sdlk_key(tmp_str); - if (key >= 0) - g_settings.input.exit_emulator_key = key; - free(tmp_str); - } } - - - diff --git a/ssnes.c b/ssnes.c index af3a1b724c..ab6201ceb3 100644 --- a/ssnes.c +++ b/ssnes.c @@ -36,14 +36,11 @@ struct global g_extern = { .video_active = true, .audio_active = true, + .game_type = SSNES_CART_NORMAL, }; // To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then. - -#define AUDIO_CHUNK_SIZE_BLOCKING 64 -#define AUDIO_CHUNK_SIZE_NONBLOCKING 2048 // So we don't get complete line-noise when fast-forwarding audio. -static size_t audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING; -void set_fast_forward_button(bool new_button_state) +static void set_fast_forward_button(bool new_button_state) { static bool old_button_state = false; static bool syncing_state = false; @@ -54,10 +51,11 @@ void set_fast_forward_button(bool new_button_state) driver.video->set_nonblock_state(driver.video_data, syncing_state); if (g_extern.audio_active) driver.audio->set_nonblock_state(driver.audio_data, (g_settings.audio.sync) ? syncing_state : true); + if (syncing_state) - audio_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING; + g_extern.audio_data.chunk_size = g_extern.audio_data.nonblock_chunk_size; else - audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING; + g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size; } old_button_state = new_button_state; } @@ -102,7 +100,9 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height) #ifdef HAVE_FILTER uint16_t output_filter[width * height * 4 * 4]; uint16_t output[width * height]; - process_frame(output, data, width, height); + + if (g_settings.video.filter != FILTER_NONE) + process_frame(output, data, width, height); switch (g_settings.video.filter) { @@ -160,37 +160,42 @@ static void audio_sample(uint16_t left, uint16_t right) } #endif - static float data[AUDIO_CHUNK_SIZE_NONBLOCKING]; - static int data_ptr = 0; + g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(*(int16_t*)&left)/0x8000; + g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(*(int16_t*)&right)/0x8000; - data[data_ptr++] = (float)(*(int16_t*)&left)/0x7FFF; - data[data_ptr++] = (float)(*(int16_t*)&right)/0x7FFF; - - if ( data_ptr >= audio_chunk_size ) + if (g_extern.audio_data.data_ptr >= g_extern.audio_data.chunk_size) { - float outsamples[audio_chunk_size * 16]; - int16_t temp_outsamples[audio_chunk_size * 16]; - SRC_DATA src_data; - src_data.data_in = data; - src_data.data_out = outsamples; - src_data.input_frames = audio_chunk_size / 2; - src_data.output_frames = audio_chunk_size * 8; + src_data.data_in = g_extern.audio_data.data; + src_data.data_out = g_extern.audio_data.outsamples; + src_data.input_frames = g_extern.audio_data.chunk_size / 2; + src_data.output_frames = g_extern.audio_data.chunk_size * 8; src_data.end_of_input = 0; src_data.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate; src_process(g_extern.source, &src_data); - src_float_to_short_array(outsamples, temp_outsamples, src_data.output_frames_gen * 2); - - if ( driver.audio->write(driver.audio_data, temp_outsamples, src_data.output_frames_gen * 4) < 0 ) + if (driver.audio->float_samples) { - fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n"); - g_extern.audio_active = false; + if (driver.audio->write(driver.audio_data, g_extern.audio_data.outsamples, src_data.output_frames_gen * sizeof(float) * 2) < 0) + { + fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n"); + g_extern.audio_active = false; + } + } + else + { + src_float_to_short_array(g_extern.audio_data.outsamples, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * 2); + + if (driver.audio->write(driver.audio_data, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * sizeof(int16_t) * 2) < 0) + { + fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n"); + g_extern.audio_active = false; + } } - data_ptr = 0; + g_extern.audio_data.data_ptr = 0; } } @@ -201,7 +206,10 @@ static void input_poll(void) static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id) { - const struct snes_keybind *binds[] = { g_settings.input.binds[0], g_settings.input.binds[1] }; + const struct snes_keybind *binds[MAX_PLAYERS]; + for (int i = 0; i < MAX_PLAYERS; i++) + binds[i] = g_settings.input.binds[i]; + return driver.input->input_state(driver.input_data, binds, port, device, index, id); } @@ -234,10 +242,22 @@ static void print_help(void) puts("================================================="); puts("ssnes: Simple Super Nintendo Emulator (libsnes)"); puts("================================================="); - puts("Usage: ssnes [rom file] [-h/--help | -s/--save" FFMPEG_HELP_QUARK "]"); + puts("Usage: ssnes [rom file] [-h/--help | -c/--config | -v/--verbose | -4/--multitap | -j/--justifier | -J/--justifiers | -S/--savestate | -m/--mouse | -g/--gameboy | -b/--bsx | -B/--bsxslot | --sufamiA | --sufamiB | -p/--scope | -s/--save" FFMPEG_HELP_QUARK "]"); puts("\t-h/--help: Show this help message"); puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin"); + puts("\t-S/--savestate: Path to use for save states. If not selected, *.state will be assumed."); puts("\t-c/--config: Path for config file." SSNES_DEFAULT_CONF_PATH_STR); + puts("\t-g/--gameboy: Path to Gameboy ROM. Load SuperGameBoy as the regular rom."); + puts("\t-b/--bsx: Path to BSX rom. Load BSX BIOS as the regular rom."); + puts("\t-B/--bsxslot: Path to BSX slotted rom. Load BSX BIOS as the regular rom."); + puts("\t--sufamiA: Path to A slot of Sufami Turbo. Load Sufami base cart as regular rom."); + puts("\t--sufamiB: Path to B slot of Sufami Turbo."); + puts("\t-m/--mouse: Connect a virtual mouse into designated port of the SNES (1 or 2)."); + puts("\t\tThis argument can be specified several times to connect more mice."); + puts("\t-p/--scope: Connect a virtual SuperScope into port 2 of the SNES."); + puts("\t-j/--justifier: Connect a virtual Konami Justifier into port 2 of the SNES."); + puts("\t-J/--justifiers: Daisy chain two virtual Konami Justifiers into port 2 of the SNES."); + puts("\t-4/--multitap: Connect a multitap to port 2 of the SNES."); #ifdef HAVE_FFMPEG puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file."); @@ -260,7 +280,18 @@ static void parse_input(int argc, char *argv[]) { "record", 1, NULL, 'r' }, #endif { "verbose", 0, NULL, 'v' }, + { "gameboy", 1, NULL, 'g' }, { "config", 0, NULL, 'c' }, + { "mouse", 1, NULL, 'm' }, + { "scope", 0, NULL, 'p' }, + { "savestate", 1, NULL, 'S' }, + { "bsx", 1, NULL, 'b' }, + { "bsxslot", 1, NULL, 'B' }, + { "justifier", 0, NULL, 'j' }, + { "justifiers", 0, NULL, 'J' }, + { "multitap", 0, NULL, '4' }, + { "sufamiA", 1, NULL, 'Y' }, + { "sufamiB", 1, NULL, 'Z' }, { NULL, 0, NULL, 0 } }; @@ -272,10 +303,11 @@ static void parse_input(int argc, char *argv[]) #define FFMPEG_RECORD_ARG #endif - char optstring[] = "hs:vc:" FFMPEG_RECORD_ARG; + char optstring[] = "hs:vc:S:m:p4jJg:b:B:Y:Z:" FFMPEG_RECORD_ARG; for(;;) { int c = getopt_long(argc, argv, optstring, opts, &option_index); + int port; if (c == -1) break; @@ -286,15 +318,70 @@ static void parse_input(int argc, char *argv[]) print_help(); exit(0); + case '4': + g_extern.has_multitap = true; + break; + + case 'j': + g_extern.has_justifier = true; + break; + + case 'J': + g_extern.has_justifiers = true; + break; + case 's': - strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm)); - g_extern.savefile_name_srm[sizeof(g_extern.savefile_name_srm)-1] = '\0'; + strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm) - 1); + break; + + case 'g': + strncpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path) - 1); + g_extern.game_type = SSNES_CART_SGB; + break; + + case 'b': + strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1); + g_extern.game_type = SSNES_CART_BSX; + break; + + case 'B': + strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1); + g_extern.game_type = SSNES_CART_BSX_SLOTTED; + break; + + case 'Y': + strncpy(g_extern.sufami_rom_path[0], optarg, sizeof(g_extern.sufami_rom_path[0]) - 1); + g_extern.game_type = SSNES_CART_SUFAMI; + break; + + case 'Z': + strncpy(g_extern.sufami_rom_path[1], optarg, sizeof(g_extern.sufami_rom_path[1]) - 1); + g_extern.game_type = SSNES_CART_SUFAMI; + break; + + case 'S': + strncpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name) - 1); break; case 'v': g_extern.verbose = true; break; + case 'm': + port = strtol(optarg, NULL, 0); + if (port < 1 || port > 2) + { + SSNES_ERR("Connect mouse to port 1 or 2.\n"); + print_help(); + exit(1); + } + g_extern.has_mouse[port - 1] = true; + break; + + case 'p': + g_extern.has_scope[1] = true; + break; + case 'c': strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1); break; @@ -329,76 +416,129 @@ static void parse_input(int argc, char *argv[]) g_extern.rom_file = fopen(argv[optind], "rb"); if (g_extern.rom_file == NULL) { - SSNES_ERR("Could not open file: \"%s\"\n", optarg); + SSNES_ERR("Could not open file: \"%s\"\n", argv[optind]); exit(1); } + // strl* would be nice :D if (strlen(g_extern.savefile_name_srm) == 0) - fill_pathname(g_extern.savefile_name_srm, argv[optind], ".srm"); + { + strcpy(g_extern.savefile_name_srm, g_extern.basename); + size_t len = strlen(g_extern.savefile_name_srm); + strncat(g_extern.savefile_name_srm, ".srm", sizeof(g_extern.savefile_name_srm) - len - 1); + } + if (strlen(g_extern.savestate_name) == 0) + { + strcpy(g_extern.savestate_name, g_extern.basename); + size_t len = strlen(g_extern.savestate_name); + strncat(g_extern.savestate_name, ".state", sizeof(g_extern.savestate_name) - len - 1); + } } else if (strlen(g_extern.savefile_name_srm) == 0) { - SSNES_ERR("Need savefile argument when reading rom from stdin.\n"); + SSNES_ERR("Need savefile path argument (--save) when reading rom from stdin.\n"); + print_help(); + exit(1); + } + else if (strlen(g_extern.savestate_name) == 0) + { + SSNES_ERR("Need savestate path argument (--savefile) when reading rom from stdin.\n"); print_help(); exit(1); } } -int main(int argc, char *argv[]) +// TODO: Add rest of the controllers. +static void init_controllers(void) { - parse_input(argc, argv); - parse_config(); - init_dlsym(); - - psnes_init(); - if (strlen(g_extern.basename) > 0) - psnes_set_cartridge_basename(g_extern.basename); - - SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor()); - void *rom_buf; - ssize_t rom_len = 0; - if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1) + if (g_extern.has_justifier) { - SSNES_ERR("Could not read ROM file.\n"); - exit(1); + SSNES_LOG("Connecting Justifier to port 2.\n"); + psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_JUSTIFIER); } - SSNES_LOG("ROM size: %d bytes\n", (int)rom_len); - - if (g_extern.rom_file != NULL) - fclose(g_extern.rom_file); - - char statefile_name[strlen(g_extern.savefile_name_srm)+strlen(".state")+1]; - char savefile_name_rtc[strlen(g_extern.savefile_name_srm)+strlen(".rtc")+1]; - - fill_pathname(statefile_name, argv[1], ".state"); - fill_pathname(savefile_name_rtc, argv[1], ".rtc"); - - init_drivers(); - - psnes_set_video_refresh(video_frame); - psnes_set_audio_sample(audio_sample); - psnes_set_input_poll(input_poll); - psnes_set_input_state(input_state); - - if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len)) + else if (g_extern.has_justifiers) { - SSNES_ERR("ROM file is not valid!\n"); - goto error; + SSNES_LOG("Connecting Justifiers to port 2.\n"); + psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_JUSTIFIERS); } - - free(rom_buf); - - unsigned serial_size = psnes_serialize_size(); - uint8_t *serial_data = malloc(serial_size); - if (serial_data == NULL) + else if (g_extern.has_multitap) { - SSNES_ERR("Failed to allocate memory for states!\n"); - goto error; + SSNES_LOG("Connecting multitap to port 2.\n"); + psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_MULTITAP); } + else + { + for (int i = 0; i < 2; i++) + { + if (g_extern.has_mouse[i]) + { + SSNES_LOG("Connecting mouse to port %d\n", i + 1); + psnes_set_controller_port_device(i, SNES_DEVICE_MOUSE); + } + else if (g_extern.has_scope[i]) + { + SSNES_LOG("Connecting scope to port %d\n", i + 1); + psnes_set_controller_port_device(i, SNES_DEVICE_SUPER_SCOPE); + } + } + } +} + +static inline void load_save_files(void) +{ + switch (g_extern.game_type) + { + case SSNES_CART_NORMAL: + case SSNES_CART_SGB: + load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); + load_save_file(g_extern.savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC); + break; + + case SSNES_CART_BSX: + case SSNES_CART_BSX_SLOTTED: + load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM); + load_save_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM); + break; + + case SSNES_CART_SUFAMI: + load_save_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM); + load_save_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM); + break; + + default: + break; + } +} + +static inline void save_files(void) +{ + switch (g_extern.game_type) + { + case SSNES_CART_NORMAL: + case SSNES_CART_SGB: + save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); + save_file(g_extern.savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC); + break; + + case SSNES_CART_BSX: + case SSNES_CART_BSX_SLOTTED: + save_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM); + save_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM); + break; + + case SSNES_CART_SUFAMI: + save_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM); + save_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM); + break; + + default: + break; + } +} - load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); - load_save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC); #ifdef HAVE_FFMPEG +static void init_recording(void) +{ // Hardcode these options at the moment. Should be specificed in the config file later on. if (g_extern.recording) { @@ -413,7 +553,7 @@ int main(int argc, char *argv[]) .channels = 2, .samplerate = 32040, .filename = g_extern.record_path, - .fps = snes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps, + .fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps, .aspect_ratio = 4.0/3 }; SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path); @@ -424,42 +564,116 @@ int main(int argc, char *argv[]) g_extern.recording = false; } } +} + +static void deinit_recording(void) +{ + if (g_extern.recording) + { + ffemu_finalize(g_extern.rec); + ffemu_free(g_extern.rec); + } +} #endif +static void fill_pathnames(void) +{ + switch (g_extern.game_type) + { + case SSNES_CART_BSX: + case SSNES_CART_BSX_SLOTTED: + // BSX PSRM + fill_pathname(g_extern.savefile_name_psrm, g_extern.savefile_name_srm, ".psrm"); + break; + + case SSNES_CART_SUFAMI: + // SUFAMI ARAM + fill_pathname(g_extern.savefile_name_asrm, g_extern.savefile_name_srm, ".asrm"); + // SUFAMI BRAM + fill_pathname(g_extern.savefile_name_bsrm, g_extern.savefile_name_srm, ".bsrm"); + break; + + default: + // Infer .rtc save path from save ram path. + fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc"); + } +} + + +int main(int argc, char *argv[]) +{ + parse_input(argc, argv); + parse_config(); + init_dlsym(); + + psnes_init(); + if (strlen(g_extern.basename) > 0) + psnes_set_cartridge_basename(g_extern.basename); + + SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor()); + + fill_pathnames(); + + if (!init_rom_file(g_extern.game_type)) + goto error; + + init_drivers(); + + psnes_set_video_refresh(video_frame); + psnes_set_audio_sample(audio_sample); + psnes_set_input_poll(input_poll); + psnes_set_input_state(input_state); + + init_controllers(); + + unsigned serial_size = psnes_serialize_size(); + uint8_t *serial_data = malloc(serial_size); + if (serial_data == NULL) + { + SSNES_ERR("Failed to allocate memory for states!\n"); + goto error; + } + + load_save_files(); + +#ifdef HAVE_FFMPEG + init_recording(); +#endif + + // Main loop for(;;) { - if (driver.input->key_pressed(driver.input_data, g_settings.input.exit_emulator_key) || + // Time to drop? + if (driver.input->key_pressed(driver.input_data, SSNES_QUIT_KEY) || !driver.video->alive(driver.video_data)) break; - if (driver.input->key_pressed(driver.input_data, g_settings.input.save_state_key)) - { - write_file(statefile_name, serial_data, serial_size); - } + set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY)); - else if (driver.input->key_pressed(driver.input_data, g_settings.input.load_state_key)) - load_state(statefile_name, serial_data, serial_size); + // Save or load state here. + if (driver.input->key_pressed(driver.input_data, SSNES_SAVE_STATE_KEY)) + write_file(g_extern.savestate_name, serial_data, serial_size); + else if (driver.input->key_pressed(driver.input_data, SSNES_LOAD_STATE_KEY)) + load_state(g_extern.savestate_name, serial_data, serial_size); - else if (driver.input->key_pressed(driver.input_data, g_settings.input.toggle_fullscreen_key)) + // If we go fullscreen we drop all drivers and reinit to be safe. + else if (driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY)) { g_settings.video.fullscreen = !g_settings.video.fullscreen; uninit_drivers(); init_drivers(); } + // Run libsnes for one frame. psnes_run(); } #ifdef HAVE_FFMPEG - if (g_extern.recording) - { - ffemu_finalize(g_extern.rec); - ffemu_free(g_extern.rec); - } + deinit_recording(); #endif - save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); - save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC); + // Flush out SRAM (and RTC) + save_files(); psnes_unload_cartridge(); psnes_term(); diff --git a/ssnes.cfg b/ssnes.cfg index 7f52b5961f..d2e372aef2 100644 --- a/ssnes.cfg +++ b/ssnes.cfg @@ -88,7 +88,13 @@ # input_player1_up = up # input_player1_down = down +# If desired, it is possible to override which joypads are being used for player 1 and 2. First joypad available is 0. +# input_player1_joypad_index = 0 +# input_player2_joypad_index = 1 + # Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output. +# You can use joypad hats with hnxx, where n is the hat, and xx is a string representing direction. +# E.g. "h0up" # input_player1_a_btn = 1 # input_player1_b_btn = 0 # input_player1_y_btn = 2 @@ -102,7 +108,9 @@ # input_player1_up_btn = 13 # input_player1_down_btn = 14 -# Axis for DPAD. Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number. +# Axis for DPAD. +# Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number. +# Do note that every other input option has the corresponding _btn and _axis binds as well; they are omitted here for clarity. # input_player1_left_axis = -0 # input_player1_right_axis = +0 # input_player1_up_axis = +1 @@ -138,8 +146,8 @@ # input_player2_left_axis = -0 # input_player2_right_axis = +0 -# input_player2_up_axis = +1 -# input_player2_down_axis = -1 +# input_player2_up_axis = -1 +# input_player2_down_axis = +1 # Toggles fullscreen. # input_toggle_fullscreen = f @@ -150,8 +158,7 @@ # Toggles between fast-forwarding and normal speed. # input_toggle_fast_forward = space -# Same, just mapping to a joypad button. -# input_toggle_fast_forward_btn = 10 -# Key to exit emulator cleanly. +# Key to exit emulator cleanly. +# Killing it in any hard way (SIGTERM, SIGKILL, etc, will terminate emulator without saving RAM, etc.) # input_exit_emulator = escape diff --git a/tools/main-stub.c b/tools/main-stub.c new file mode 100644 index 0000000000..0fb5bf8585 --- /dev/null +++ b/tools/main-stub.c @@ -0,0 +1,6 @@ +int real_main(int argc, char *argv[]); + +int main(int argc, char *argv[]) +{ + return real_main(argc, argv); +} diff --git a/tools/ssnes-joyconfig.c b/tools/ssnes-joyconfig.c new file mode 100644 index 0000000000..b5c0ee8c23 --- /dev/null +++ b/tools/ssnes-joyconfig.c @@ -0,0 +1,284 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SDL +#include "SDL.h" +#endif + +#include "conf/config_file.h" +#include +#include +#include +#include "general.h" + + +static int g_player = 1; +static int g_joypad = 1; +static char *g_in_path = NULL; +static char *g_out_path = NULL; + +static void print_help(void) +{ + puts("=================="); + puts("ssnes-joyconfig"); + puts("=================="); + puts("Usage: ssnes-joyconfig [ -p/--player <1|2> | -j/--joypad | -i/--input | -o/--output | -h/--help ]"); + puts(""); + puts("-p/--player: Which player to configure for (1 or 2)."); + puts("-j/--joypad: Which joypad to use when configuring (first joypad is 1)."); + puts("-i/--input: Input file to configure with. Binds will be added on or overwritten."); + puts("\tIf not selected, an empty config will be used as a base."); + puts("-o/--output: Output file to write to. If not selected, config file will be dumped to stdout."); + puts("-h/--help: This help."); +} + +struct bind +{ + char *keystr; + char *confbtn[MAX_PLAYERS]; + char *confaxis[MAX_PLAYERS]; +}; + +#define BIND(x, k) { x, { "input_player1_" #k "_btn", "input_player2_" #k "_btn", "input_player3_" #k "_btn", "input_player4_" #k "_btn", "input_player5_" #k "_btn" }, {"input_player1_" #k "_axis", "input_player2_" #k "_axis", "input_player3_" #k "_axis", "input_player4_" #k "_axis", "input_player5_" #k "_axis"}}, +static struct bind binds[] = { + BIND("A button (right)", a) + BIND("B button (down)", b) + BIND("X button (top)", x) + BIND("Y button (left)", y) + BIND("L button (left shoulder)", l) + BIND("R button (right shoulder)", r) + BIND("Start button", start) + BIND("Select button", select) + BIND("Left D-pad", left) + BIND("Up D-pad", up) + BIND("Right D-pad", right) + BIND("Down D-pad", down) +}; + +void get_binds(config_file_t *conf, int player, int joypad) +{ + if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO) < 0) + { + fprintf(stderr, "Failed to init joystick subsystem.\n"); + exit(1); + } + SDL_Joystick *joystick; + int num = SDL_NumJoysticks(); + if (joypad >= num) + { + fprintf(stderr, "Cannot find joystick number %d, only have %d joysticks available ...\n", joypad + 1, num); + exit(1); + } + + joystick = SDL_JoystickOpen(joypad); + if (!joystick) + { + fprintf(stderr, "Cannot open joystick.\n"); + exit(1); + } + + SDL_JoystickUpdate(); + + int last_axis = 0xFF; + int num_axes = SDL_JoystickNumAxes(joystick); + int initial_axes[num_axes]; + for (int i = 0; i < num_axes; i++) + initial_axes[i] = SDL_JoystickGetAxis(joystick, i); + + + fprintf(stderr, "Configuring binds for player #%d on joypad #%d (%s)\n", player + 1, joypad + 1, SDL_JoystickName(joypad)); + fprintf(stderr, "Press Ctrl-C to exit early.\n"); + fprintf(stderr, "\n"); + + for (unsigned i = 0; i < sizeof(binds) / sizeof(struct bind); i++) + { + fprintf(stderr, "%s\n", binds[i].keystr); + + bool done = false; + SDL_Event event; + int value; + const char *quark; + + while (SDL_WaitEvent(&event) && !done) + { + switch (event.type) + { + case SDL_JOYBUTTONDOWN: + fprintf(stderr, "\tJoybutton pressed: %d\n", (int)event.jbutton.button); + done = true; + config_set_int(conf, binds[i].confbtn[player], event.jbutton.button); + break; + + case SDL_JOYAXISMOTION: + if (abs(event.jaxis.value) > 20000 && + abs((int)event.jaxis.value - initial_axes[event.jaxis.axis]) > 20000 && + event.jaxis.axis != last_axis) + { + last_axis = event.jaxis.axis; + fprintf(stderr, "\tJoyaxis moved: Axis %d, Value %d\n", (int)event.jaxis.axis, (int)event.jaxis.value); + done = true; + + char buf[8]; + snprintf(buf, sizeof(buf), event.jaxis.value > 0 ? "+%d" : "-%d", event.jaxis.axis); + config_set_string(conf, binds[i].confaxis[player], buf); + } + break; + + case SDL_JOYHATMOTION: + value = event.jhat.value; + if (value & SDL_HAT_UP) + quark = "up"; + else if (value & SDL_HAT_DOWN) + quark = "down"; + else if (value & SDL_HAT_LEFT) + quark = "left"; + else if (value & SDL_HAT_RIGHT) + quark = "right"; + else + break; + + fprintf(stderr, "\tJoyhat moved: Hat %d, direction %s\n", (int)event.jhat.hat, quark); + + done = true; + char buf[16]; + snprintf(buf, sizeof(buf), "h%d%s", event.jhat.hat, quark); + config_set_string(conf, binds[i].confbtn[player], buf); + break; + + + case SDL_QUIT: + goto end; + + default: + break; + } + } + } + +end: + SDL_JoystickClose(joystick); + SDL_Quit(); +} + +static void parse_input(int argc, char *argv[]) +{ + char optstring[] = "i:o:p:j:h"; + struct option opts[] = { + { "input", 1, NULL, 'i' }, + { "output", 1, NULL, 'o' }, + { "player", 1, NULL, 'p' }, + { "joypad", 1, NULL, 'j' }, + { "help", 0, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int option_index = 0; + for(;;) + { + int c = getopt_long(argc, argv, optstring, opts, &option_index); + if (c == -1) + break; + + switch (c) + { + case 'h': + print_help(); + exit(0); + + case 'i': + g_in_path = strdup(optarg); + break; + + case 'o': + g_out_path = strdup(optarg); + break; + + case 'j': + g_joypad = strtol(optarg, NULL, 0); + if (g_joypad < 1) + { + fprintf(stderr, "Joypad number can't be less than 1!\n"); + exit(1); + } + break; + + case 'p': + g_player = strtol(optarg, NULL, 0); + if (g_player < 1) + { + fprintf(stderr, "Player number must be at least 1!\n"); + exit(1); + } + else if (g_player > MAX_PLAYERS) + { + fprintf(stderr, "Player number must be from 1 to %d.\n", MAX_PLAYERS); + exit(1); + } + break; + + default: + break; + } + } + + if (optind < argc) + { + print_help(); + exit(1); + } + +} + +// Windows is being bitchy. Cannot include SDL.h with a file that has main() it seems ... It simply won't run at all even with -lSDLmain. +#ifdef _WIN32 +int real_main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + parse_input(argc, argv); + + config_file_t *conf = config_file_new(g_in_path); + if (!conf) + { + fprintf(stderr, "Couldn't open config file ...\n"); + return 1; + } + + const char *index_list[] = { + "input_player1_joypad_index", + "input_player2_joypad_index", + "input_player3_joypad_index", + "input_player4_joypad_index", + "input_player5_joypad_index" + }; + + config_set_int(conf, index_list[g_player - 1], g_joypad - 1); + + get_binds(conf, g_player - 1, g_joypad - 1); + config_file_write(conf, g_out_path); + config_file_free(conf); + if (g_in_path) + free(g_in_path); + if (g_out_path) + free(g_out_path); + return 0; +}