diff --git a/Makefile b/Makefile
index 94f52f093a..3972d52e75 100644
--- a/Makefile
+++ b/Makefile
@@ -2,43 +2,44 @@ include config.mk
TARGET = ssnes
-DEFINES =
-OBJ = ssnes.o file.o driver.o
-libsnes = -lsnes
+OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o
-LIBS = -lsamplerate $(libsnes)
+LIBS = -lsamplerate
-ifeq ($(BUILD_RSOUND), 1)
+ifeq ($(HAVE_RSOUND), 1)
OBJ += audio/rsound.o
LIBS += -lrsound
endif
-ifeq ($(BUILD_OSS), 1)
+ifeq ($(HAVE_OSS), 1)
OBJ += audio/oss.o
endif
-ifeq ($(BUILD_ALSA), 1)
+ifeq ($(HAVE_ALSA), 1)
OBJ += audio/alsa.o
LIBS += -lasound
endif
-ifeq ($(BUILD_ROAR), 1)
+ifeq ($(HAVE_ROAR), 1)
OBJ += audio/roar.o
LIBS += -lroar
endif
-ifeq ($(BUILD_AL), 1)
+ifeq ($(HAVE_AL), 1)
OBJ += audio/openal.o
LIBS += -lopenal
endif
+ifeq ($(HAVE_JACK),1)
+ OBJ += audio/jack.o
+ LIBS += -ljack
+endif
-ifeq ($(BUILD_OPENGL), 1)
+ifeq ($(HAVE_GLFW), 1)
OBJ += gfx/gl.o
LIBS += -lglfw
endif
-ifeq ($(BUILD_CG), 1)
+ifeq ($(HAVE_CG), 1)
LIBS += -lCg -lCgGL
- DEFINES += -DHAVE_CG
endif
-ifeq ($(BUILD_FILTER), 1)
+ifeq ($(HAVE_FILTER), 1)
OBJ += hqflt/hq.o
OBJ += hqflt/grayscale.o
OBJ += hqflt/bleed.o
@@ -46,9 +47,19 @@ ifeq ($(BUILD_FILTER), 1)
OBJ += hqflt/snes_ntsc/snes_ntsc.o
endif
-CFLAGS = -Wall -O3 -std=gnu99 -Wno-unused-variable -I. $(DEFINES)
+ifeq ($(HAVE_DYNAMIC), 1)
+ LIBS += -ldl
+else
+ LIBS += $(libsnes)
+endif
-all: $(TARGET)
+CFLAGS = -Wall -O3 -g -std=gnu99 -I.
+
+all: $(TARGET) config.mk
+
+config.mk: configure qb/*
+ @echo "config.mk is outdated or non-existing. Run ./configure again."
+ @exit 1
ssnes: $(OBJ)
$(CXX) -o $@ $(OBJ) $(LIBS) $(CFLAGS)
@@ -57,14 +68,16 @@ ssnes: $(OBJ)
$(CC) $(CFLAGS) -c -o $@ $<
install: $(TARGET)
- install -m755 $(TARGET) $(PREFIX)/bin
+ install -m755 $(TARGET) $(DESTDIR)/$(PREFIX)/bin
+ install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg
uninstall: $(TARGET)
- rm -rf $(PREFIX)/bin/$(TARGET)
+ rm -rf $(DESTDIR)/$(PREFIX)/bin/$(TARGET)
clean:
rm -f *.o
rm -f audio/*.o
+ rm -f conf/*.o
rm -f gfx/*.o
rm -f hqflt/*.o
rm -f hqflt/snes_ntsc/*.o
diff --git a/README.md b/README.md
index ab32262042..a6ce0b1d0c 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,7 @@ This enables the possibility of custom front-ends for the emulator.
# Philosophy
SSNES attempts to be very small and lean, while still having all the useful core features expected from an emulator.
-It is close in spirit to suckless' DWM, in that configuring the emulator requires a recompile.
-The configuration is done through editing a C header file.
-C programming skills are not necessary to configure it (no programming involved), but some basic programming experience might be needed.
+It is used through command-line.
# Dependencies
@@ -33,6 +31,7 @@ SSNES needs one of these audio driver libraries:
- RoarAudio
- RSound
- OpenAL
+ - JACK
# Building libsnes
@@ -46,17 +45,26 @@ SSNES needs one of these audio driver libraries:
# Configuring
-SSNES configuring is done through editing config.h and config.mk.
-The default configs can be found in config.h.def and config.mk.def respectively.
-Do note that you might have to edit config.mk if you edit driver and filter options!
-By default, ALSA audio driver is assumed.
+The default configuration is defined in config.def.h.
+These can later be tweaked by using the ssnes config file.
+A sample configuration file is installed to /etc/ssnes.cfg.
+This is the system-wide config file.
+Each user should create a config file in $XDG\_CONFIG\_HOME/ssnes/ssnes.cfg.
+The users only need to configure a certain option if the desired value deviates from the value defined in config.def.h.
-Most options in config.h should be self-explanatory.
To configure joypads, start up jstest /dev/input/js0 to determine which joypad buttons (and axis) to use.
# Compiling and installing
-The good old make && sudo make install should do the trick :)
+As most packages, SSNES is built using the standard ./configure && make && make install
+Do note that the build system is not autotools based, but resembles it.
+
+Notable options for ./configure:
+--with-libsnes=: Normally libsnes is located with -lsnes, however, this can be overridden.
+--enable-dynamic: Do not link to libsnes at compile time, but load libsnes dynamically at runtime. libsnes\_path in config file defines which library to load. Useful for development.
+
+Do note that these two options are mutually exclusive.
+
# Filters and Cg shader support
@@ -67,3 +75,5 @@ Cg shaders are compiled at run-time, and shaders could be dropped in.
All shaders share a common interface to pass some essential arguments such as texture size and viewport size. (Common for pixel art scalers)
Some Cg shaders are included in hqflt/cg/ and could be used as an example.
+While these shaders are Cg, they closely resemble the GLSL shaders found in bSNES shader pack, so porting them is trivial.
+
diff --git a/audio/alsa.c b/audio/alsa.c
index 5dece09311..4e6145141e 100644
--- a/audio/alsa.c
+++ b/audio/alsa.c
@@ -190,7 +190,8 @@ const audio_driver_t audio_alsa = {
.stop = __alsa_stop,
.start = __alsa_start,
.set_nonblock_state = __alsa_set_nonblock_state,
- .free = __alsa_free
+ .free = __alsa_free,
+ .ident = "alsa"
};
diff --git a/audio/jack.c b/audio/jack.c
new file mode 100644
index 0000000000..01ae1c9ef6
--- /dev/null
+++ b/audio/jack.c
@@ -0,0 +1,300 @@
+/* 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 .
+ */
+
+
+#include "driver.h"
+#include
+#include "general.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define FRAMES(x) (x / (sizeof(int16_t) * 2))
+#define SAMPLES(x) (x / sizeof(int16_t))
+
+typedef struct jack
+{
+ jack_client_t *client;
+ jack_port_t *ports[2];
+ jack_ringbuffer_t *buffer[2];
+ volatile bool shutdown;
+ bool nonblock;
+
+ pthread_cond_t cond;
+ pthread_mutex_t cond_lock;
+} jack_t;
+
+static int process_cb(jack_nframes_t nframes, void *data)
+{
+ jack_t *jd = data;
+ if (nframes <= 0)
+ {
+ pthread_cond_signal(&jd->cond);
+ return 0;
+ }
+
+ jack_nframes_t avail[2];
+ avail[0] = jack_ringbuffer_read_space(jd->buffer[0]);
+ avail[1] = jack_ringbuffer_read_space(jd->buffer[1]);
+ jack_nframes_t min_avail = ((avail[0] < avail[1]) ? avail[0] : avail[1]) / sizeof(jack_default_audio_sample_t);
+
+ 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);
+ assert(out);
+ jack_ringbuffer_read(jd->buffer[i], (char*)out, min_avail * sizeof(jack_default_audio_sample_t));
+
+ for (jack_nframes_t f = min_avail; f < nframes; f++)
+ {
+ out[f] = 0.0f;
+ }
+ }
+ pthread_cond_signal(&jd->cond);
+ return 0;
+}
+
+static void shutdown_cb(void *data)
+{
+ jack_t *jd = data;
+ jd->shutdown = true;
+ 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)
+{
+ int parsed = 0;
+
+ const char *con = strtok(g_settings.audio.device, ",");
+ if (con)
+ dest_ports[parsed++] = con;
+ con = strtok(NULL, ",");
+ if (con)
+ dest_ports[parsed++] = con;
+
+ for (int i = parsed; i < 2; i++)
+ dest_ports[i] = jports[i];
+}
+
+static void* __jack_init(const char* device, int rate, int latency)
+{
+ jack_t *jd = calloc(1, sizeof(jack_t));
+ if ( jd == NULL )
+ return NULL;
+
+ const char **jports = NULL;
+
+ jd->client = jack_client_open("SSNES", JackNullOption, NULL);
+ if (jd->client == NULL)
+ goto error;
+
+ g_settings.audio.out_rate = jack_get_sample_rate(jd->client);
+
+ jack_set_process_callback(jd->client, process_cb, jd);
+ jack_on_shutdown(jd->client, shutdown_cb, jd);
+
+ jd->ports[0] = jack_port_register(jd->client, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ jd->ports[1] = jack_port_register(jd->client, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (jd->ports[0] == NULL || jd->ports[1] == NULL)
+ {
+ SSNES_ERR("Failed to register ports.\n");
+ 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];
+ jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput);
+ if (jports == NULL)
+ {
+ SSNES_ERR("Failed to get ports.\n");
+ goto error;
+ }
+
+ parse_ports(dest_ports, jports);
+
+ if (jack_activate(jd->client) < 0)
+ {
+ SSNES_ERR("Failed to activate Jack...\n");
+ goto error;
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ if (jack_connect(jd->client, jack_port_name(jd->ports[i]), dest_ports[i]))
+ {
+ SSNES_ERR("Failed to connect to Jack port.\n");
+ goto error;
+ }
+ }
+
+ pthread_cond_init(&jd->cond, NULL);
+ pthread_mutex_init(&jd->cond_lock, NULL);
+
+
+ jack_free(jports);
+ return jd;
+
+error:
+ if (jports != NULL)
+ jack_free(jports);
+ return NULL;
+}
+
+static size_t write_buffer(jack_t *jd, const void *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];
+
+ for(;;)
+ {
+ if (jd->shutdown)
+ return 0;
+
+ size_t avail[2];
+ 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];
+
+ if (jd->nonblock)
+ {
+ 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;
+ }
+
+ 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);
+ }
+
+ 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;
+}
+
+static ssize_t __jack_write(void* data, const void* buf, size_t size)
+{
+ jack_t *jd = data;
+
+ return write_buffer(jd, buf, size);
+}
+
+static bool __jack_stop(void *data)
+{
+ (void)data;
+ return true;
+}
+
+static void __jack_set_nonblock_state(void *data, bool state)
+{
+ jack_t *jd = data;
+ jd->nonblock = state;
+}
+
+static bool __jack_start(void *data)
+{
+ (void)data;
+ return true;
+}
+
+static void __jack_free(void *data)
+{
+ jack_t *jd = data;
+
+ jd->shutdown = true;
+
+ if (jd->client != NULL)
+ {
+ jack_deactivate(jd->client);
+ jack_client_close(jd->client);
+ }
+
+ for (int i = 0; i < 2; i++)
+ if (jd->buffer[i] != NULL)
+ jack_ringbuffer_free(jd->buffer[i]);
+
+ pthread_mutex_destroy(&jd->cond_lock);
+ pthread_cond_destroy(&jd->cond);
+ free(jd);
+}
+
+const audio_driver_t audio_jack = {
+ .init = __jack_init,
+ .write = __jack_write,
+ .stop = __jack_stop,
+ .start = __jack_start,
+ .set_nonblock_state = __jack_set_nonblock_state,
+ .free = __jack_free,
+ .ident = "jack"
+};
+
+
+
+
+
+
diff --git a/audio/openal.c b/audio/openal.c
index 75826541e7..431acba814 100644
--- a/audio/openal.c
+++ b/audio/openal.c
@@ -233,7 +233,8 @@ const audio_driver_t audio_openal = {
.stop = __al_stop,
.start = __al_start,
.set_nonblock_state = __al_set_nonblock_state,
- .free = __al_free
+ .free = __al_free,
+ .ident = "openal"
};
diff --git a/audio/oss.c b/audio/oss.c
index b12ddebad1..bf451905e6 100644
--- a/audio/oss.c
+++ b/audio/oss.c
@@ -136,7 +136,8 @@ const audio_driver_t audio_oss = {
.stop = __oss_stop,
.start = __oss_start,
.set_nonblock_state = __oss_set_nonblock_state,
- .free = __oss_free
+ .free = __oss_free,
+ .ident = "oss"
};
diff --git a/audio/roar.c b/audio/roar.c
index db94f0768b..89ab744560 100644
--- a/audio/roar.c
+++ b/audio/roar.c
@@ -107,7 +107,8 @@ const audio_driver_t audio_roar = {
.stop = __roar_stop,
.start = __roar_start,
.set_nonblock_state = __roar_set_nonblock_state,
- .free = __roar_free
+ .free = __roar_free,
+ .ident = "roar"
};
diff --git a/audio/rsound.c b/audio/rsound.c
index 181f0859c8..08040c2e65 100644
--- a/audio/rsound.c
+++ b/audio/rsound.c
@@ -135,7 +135,8 @@ const audio_driver_t audio_rsound = {
.stop = __rsd_stop,
.start = __rsd_start,
.set_nonblock_state = __rsd_set_nonblock_state,
- .free = __rsd_free
+ .free = __rsd_free,
+ .ident = "rsound"
};
diff --git a/conf/config_file.c b/conf/config_file.c
new file mode 100644
index 0000000000..441256f9cd
--- /dev/null
+++ b/conf/config_file.c
@@ -0,0 +1,293 @@
+/* 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 .
+ */
+
+
+#include "config_file.h"
+#include
+#include
+#include
+#include
+#include "general.h"
+
+struct entry_list
+{
+ char *key;
+ char *value;
+ struct entry_list *next;
+};
+
+struct config_file
+{
+ struct entry_list *entries;
+};
+
+static char *getaline(FILE *file)
+{
+ char *newline = malloc(9);
+ size_t cur_size = 8;
+ size_t index = 0;
+
+ int in = getc(file);
+ while (in != EOF && in != '\n')
+ {
+ if (index == cur_size)
+ {
+ cur_size *= 2;
+ newline = realloc(newline, cur_size + 1);
+ }
+
+ newline[index++] = in;
+ in = getc(file);
+ }
+ newline[index] = '\0';
+ return newline;
+}
+
+static bool parse_line(struct entry_list *list, char *line)
+{
+ // Remove everything after comment.
+ char *comment = strchr(line, '#');
+ if (comment)
+ *comment = '\0';
+
+ // Skips to first character.
+ while (isspace(*line))
+ line++;
+
+ char *key = malloc(9);
+ size_t cur_size = 8;
+ size_t index = 0;
+
+ while (isgraph(*line))
+ {
+ if (index == cur_size)
+ {
+ cur_size *= 2;
+ key = realloc(key, cur_size + 1);
+ }
+
+ key[index++] = *line++;
+ }
+ key[index] = '\0';
+ list->key = key;
+
+ while (isspace(*line))
+ line++;
+
+ // If we don't have an equal sign here, we've got an invalid string...
+ if (*line != '=')
+ {
+ list->key = NULL;
+ free(key);
+ return false;
+ }
+ line++;
+
+ while (isspace(*line))
+ line++;
+
+ // We have a full string. Read until next ".
+ if (*line == '"')
+ {
+ char *tok = strtok(line + 1, "\"");
+ if (tok == NULL)
+ {
+ list->key = NULL;
+ free(key);
+ return false;
+ }
+ list->value = strdup(tok);
+ }
+ else // We don't have that... Read till next space.
+ {
+ char *tok = strtok(line, " \t\f");
+ if (tok == NULL)
+ {
+ list->key = NULL;
+ free(key);
+ return false;
+ }
+ list->value = strdup(tok);
+ }
+
+ return true;
+}
+
+static void print_config(config_file_t *conf)
+{
+ struct entry_list *tmp = conf->entries;
+ while (tmp != NULL)
+ {
+ printf("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;
+
+ struct entry_list *tail = conf->entries;
+
+ while (!feof(file))
+ {
+ struct entry_list *list = calloc(1, sizeof(*list));
+ char *line = getaline(file);
+
+ if (line)
+ {
+ if (parse_line(list, line))
+ {
+ if (conf->entries == NULL)
+ {
+ conf->entries = list;
+ tail = list;
+ }
+ else
+ {
+ tail->next = list;
+ tail = list;
+ }
+ }
+ free(line);
+ }
+ }
+ fclose(file);
+
+ if (g_extern.verbose)
+ print_config(conf);
+
+ return conf;
+}
+
+void config_file_free(config_file_t *conf)
+{
+ if (conf != NULL)
+ {
+ struct entry_list *tmp = conf->entries;
+ struct entry_list *old = tmp;
+ while (tmp != NULL)
+ {
+ free(tmp->key);
+ free(tmp->value);
+ old = tmp;
+ tmp = tmp->next;
+ free(old);
+ }
+ free(conf);
+ }
+}
+
+bool config_get_double(config_file_t *conf, const char *key, double *in)
+{
+ struct entry_list *list = conf->entries;
+
+ while (list != NULL)
+ {
+ if (strcmp(key, list->key) == 0)
+ {
+ *in = strtod(list->value, NULL);
+ return true;
+ }
+ list = list->next;
+ }
+ return false;
+}
+
+bool config_get_int(config_file_t *conf, const char *key, int *in)
+{
+ struct entry_list *list = conf->entries;
+
+ while (list != NULL)
+ {
+ if (strcmp(key, list->key) == 0)
+ {
+ *in = strtol(list->value, NULL, 0);
+ return true;
+ }
+ list = list->next;
+ }
+ return false;
+}
+
+bool config_get_char(config_file_t *conf, const char *key, char *in)
+{
+ struct entry_list *list = conf->entries;
+
+ while (list != NULL)
+ {
+ if (strcmp(key, list->key) == 0)
+ {
+ if (strlen(list->value) > 1)
+ return false;
+ *in = *list->value;
+ return true;
+ }
+ list = list->next;
+ }
+ return false;
+}
+
+bool config_get_string(config_file_t *conf, const char *key, char **str)
+{
+ struct entry_list *list = conf->entries;
+
+ while (list != NULL)
+ {
+ if (strcmp(key, list->key) == 0)
+ {
+ *str = strdup(list->value);
+ return true;
+ }
+ list = list->next;
+ }
+ return false;
+}
+
+bool config_get_bool(config_file_t *conf, const char *key, bool *in)
+{
+ struct entry_list *list = conf->entries;
+
+ while (list != NULL)
+ {
+ if (strcmp(key, list->key) == 0)
+ {
+ if (strcasecmp(list->value, "true") == 0)
+ *in = true;
+ else if (strcasecmp(list->value, "1") == 0)
+ *in = true;
+ else if (strcasecmp(list->value, "false") == 0)
+ *in = false;
+ else if (strcasecmp(list->value, "0") == 0)
+ *in = false;
+ else
+ return false;
+
+ return true;
+ }
+ list = list->next;
+ }
+ return false;
+}
+
+
diff --git a/conf/config_file.h b/conf/config_file.h
new file mode 100644
index 0000000000..1992127564
--- /dev/null
+++ b/conf/config_file.h
@@ -0,0 +1,53 @@
+/* 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 __CONFIG_FILE_H
+#define __CONFIG_FILE_H
+
+#include
+#include
+
+typedef struct config_file config_file_t;
+
+/////
+// Config file format
+// - # are treated as comments. Rest of the line is ignored.
+// - 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.
+config_file_t *config_file_new(const char *path);
+// Frees config file.
+void config_file_free(config_file_t *conf);
+
+// All extract functions return true when value is valid and exists. Returns false otherwise.
+
+// Extracts a double from config file.
+bool config_get_double(config_file_t *conf, const char *entry, double *in);
+// Extracts an int from config file.
+bool config_get_int(config_file_t *conf, const char *entry, int *in);
+// Extracts a single char. If value consists of several chars, this is an error.
+bool config_get_char(config_file_t *conf, const char *entry, char *in);
+// Extracts an allocated string in *in. This must be free()-d if this function succeeds.
+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);
+
+
+
+#endif
diff --git a/config.h.def b/config.def.h
similarity index 86%
rename from config.h.def
rename to config.def.h
index 859daec4bb..d2ce444381 100644
--- a/config.h.def
+++ b/config.def.h
@@ -37,11 +37,11 @@
#define AUDIO_ALSA 3
#define AUDIO_ROAR 4
#define AUDIO_AL 5
+#define AUDIO_JACK 6
////////////////////////
-// Chooses which video and audio subsystem to use. Remember to update config.mk if you change these.
-#define VIDEO_DRIVER VIDEO_GL
-#define AUDIO_DRIVER AUDIO_ALSA
+#define VIDEO_DEFAULT_DRIVER VIDEO_GL
+#define AUDIO_DEFAULT_DRIVER AUDIO_ALSA
////////////////
@@ -53,7 +53,7 @@ static const float xscale = 3.0; // Real x res = 296 * xscale
static const float yscale = 3.0; // Real y res = 224 * yscale
// Fullscreen
-#define START_FULLSCREEN false; // To start in Fullscreen or not
+static const bool fullscreen = false; // To start in Fullscreen or not
static const unsigned fullscreen_x = 1280;
static const unsigned fullscreen_y = 720;
@@ -63,28 +63,9 @@ static const bool vsync = true;
// Smooths picture
static const bool video_smooth = true;
-// Path to custom Cg shader. If using custom shaders, it is recommended to disable video_smooth.
-#ifdef HAVE_CG
-extern char cg_shader_path[];
-#define DEFAULT_CG_SHADER "hqflt/cg/quad.cg"
-#endif
-
// On resize and fullscreen, rendering area will stay 4:3
static const bool force_aspect = true;
-/////////// Video filters (CPU based)
-#define FILTER_NONE 0
-#define FILTER_HQ2X 1
-#define FILTER_HQ4X 2
-#define FILTER_GRAYSCALE 3
-#define FILTER_BLEED 4
-#define FILTER_NTSC 5
-////////////////////////
-
-// If you change this to something other than FILTER_NONE, make sure that you build the filter module in config.mk.
-#define VIDEO_FILTER FILTER_NONE
-
-
////////////////
// Audio
////////////////
@@ -124,8 +105,6 @@ static const bool audio_sync = true;
#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF)
#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U)
-#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF)
-#define AXIS_POS_GET(x) (x & 0xFFFF)
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
// To figure out which joypad buttons to use, check jstest or similar.
diff --git a/config.mk.def b/config.mk.def
deleted file mode 100644
index 9d677deeae..0000000000
--- a/config.mk.def
+++ /dev/null
@@ -1,13 +0,0 @@
-
-BUILD_OPENGL = 1
-BUILD_CG = 0
-BUILD_FILTER = 0
-
-BUILD_RSOUND = 0
-BUILD_OSS = 0
-BUILD_ALSA = 1
-BUILD_ROAR = 0
-BUILD_AL = 0
-
-
-PREFIX = /usr/local
diff --git a/configure b/configure
new file mode 100755
index 0000000000..d50a2b8eae
--- /dev/null
+++ b/configure
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+echo ""
+
+. qb/config.params.sh
+
+parse_input "$@"
+
+. qb/qb.comp.sh
+. qb/config.libs.sh
+
+
+
diff --git a/driver.c b/driver.c
index 5a4fc2f68b..4e694af930 100644
--- a/driver.c
+++ b/driver.c
@@ -17,9 +17,74 @@
#include "driver.h"
-#include "config.h"
#include "general.h"
#include
+#include
+#include "hqflt/filters.h"
+#include "config.h"
+
+static const audio_driver_t *audio_drivers[] = {
+#ifdef HAVE_ALSA
+ &audio_alsa,
+#endif
+#ifdef HAVE_OSS
+ &audio_oss,
+#endif
+#ifdef HAVE_RSOUND
+ &audio_rsound,
+#endif
+#ifdef HAVE_AL
+ &audio_openal,
+#endif
+#ifdef HAVE_ROAR
+ &audio_roar,
+#endif
+#ifdef HAVE_JACK
+ &audio_jack,
+#endif
+};
+
+static const video_driver_t *video_drivers[] = {
+#ifdef HAVE_GLFW
+ &video_gl,
+#endif
+};
+
+static void find_audio_driver(void)
+{
+ for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
+ {
+ if (strcasecmp(g_settings.audio.driver, audio_drivers[i]->ident) == 0)
+ {
+ driver.audio = audio_drivers[i];
+ return;
+ }
+ }
+ SSNES_ERR("Couldn't find any audio driver named \"%s\"\n", g_settings.audio.driver);
+ fprintf(stderr, "Available audio drivers are:\n");
+ for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
+ fprintf(stderr, "\t%s\n", audio_drivers[i]->ident);
+
+ exit(1);
+}
+
+static void find_video_driver(void)
+{
+ for (int i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++)
+ {
+ if (strcasecmp(g_settings.video.driver, video_drivers[i]->ident) == 0)
+ {
+ driver.video = video_drivers[i];
+ return;
+ }
+ }
+ SSNES_ERR("Couldn't find any video driver named \"%s\"\n", g_settings.video.driver);
+ fprintf(stderr, "Available video drivers are:\n");
+ for (int i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++)
+ fprintf(stderr, "\t%s\n", video_drivers[i]->ident);
+
+ exit(1);
+}
void init_drivers(void)
{
@@ -35,73 +100,76 @@ void uninit_drivers(void)
void init_audio(void)
{
- if (!audio_enable)
+ if (!g_settings.audio.enable)
{
- audio_active = false;
+ g_extern.audio_active = false;
return;
}
- driver.audio_data = driver.audio->init(audio_device, out_rate, out_latency);
- if ( driver.audio_data == NULL )
- audio_active = false;
+ find_audio_driver();
- if (!audio_sync && audio_active)
+ 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);
int err;
- source = src_new(SAMPLERATE_QUALITY, 2, &err);
- if (!source)
- audio_active = false;
+ g_extern.source = src_new(g_settings.audio.src_quality, 2, &err);
+ if (!g_extern.source)
+ g_extern.audio_active = false;
}
void uninit_audio(void)
{
- if (!audio_enable)
+ if (!g_settings.audio.enable)
{
- audio_active = false;
+ g_extern.audio_active = false;
return;
}
if ( driver.audio_data && driver.audio )
driver.audio->free(driver.audio_data);
- if ( source )
- src_delete(source);
+ if ( g_extern.source )
+ src_delete(g_extern.source);
}
void init_video_input(void)
{
- int scale;
+ int scale = 2;
+
+ find_video_driver();
// We multiply scales with 2 to allow for hi-res games.
-#if VIDEO_FILTER == FILTER_NONE
- scale = 2;
-#elif VIDEO_FILTER == FILTER_HQ2X
- scale = 4;
-#elif VIDEO_FILTER == FILTER_HQ4X
- scale = 8;
-#elif VIDEO_FILTER == FILTER_NTSC
- scale = 8;
-#elif VIDEO_FILTER == FILTER_GRAYSCALE
- scale = 2;
-#elif VIDEO_FILTER == FILTER_BLEED
- scale = 2;
-#else
- scale = 2;
+#if HAVE_FILTER
+ switch (g_settings.video.filter)
+ {
+ case FILTER_HQ2X:
+ scale = 4;
+ break;
+ case FILTER_HQ4X:
+ case FILTER_NTSC:
+ scale = 8;
+ break;
+ default:
+ break;
+ }
#endif
video_info_t video = {
- .width = (fullscreen) ? fullscreen_x : (296 * xscale),
- .height = (fullscreen) ? fullscreen_y : (224 * yscale),
- .fullscreen = fullscreen,
- .vsync = vsync,
- .force_aspect = force_aspect,
- .smooth = video_smooth,
+ .width = (g_settings.video.fullscreen) ? g_settings.video.fullscreen_x : (296 * g_settings.video.xscale),
+ .height = (g_settings.video.fullscreen) ? g_settings.video.fullscreen_y : (224 * g_settings.video.yscale),
+ .fullscreen = g_settings.video.fullscreen,
+ .vsync = g_settings.video.vsync,
+ .force_aspect = g_settings.video.force_aspect,
+ .smooth = g_settings.video.smooth,
.input_scale = scale,
};
const input_driver_t *tmp = driver.input;
- driver.video_data = driver.video->init(&video, &(driver.input));
+ driver.video_data = driver.video->init(&video, &driver.input);
if ( driver.video_data == NULL )
{
@@ -139,9 +207,9 @@ void uninit_video_input(void)
driver.input->free(driver.input_data);
}
-bool video_active = true;
-bool audio_active = true;
+driver_t driver;
+#if 0
driver_t driver = {
#if VIDEO_DRIVER == VIDEO_GL
.video = &video_gl,
@@ -149,12 +217,12 @@ driver_t driver = {
#error "Define a valid video driver in config.h"
#endif
-#if AUDIO_DRIVER == AUDIO_RSOUND
+#if AUDIO_DRIVER == AUDIO_ALSA
+ .audio = &audio_alsa,
+#elif AUDIO_DRIVER == AUDIO_RSOUND
.audio = &audio_rsound,
#elif AUDIO_DRIVER == AUDIO_OSS
.audio = &audio_oss,
-#elif AUDIO_DRIVER == AUDIO_ALSA
- .audio = &audio_alsa,
#elif AUDIO_DRIVER == AUDIO_ROAR
.audio = &audio_roar,
#elif AUDIO_DRIVER == AUDIO_AL
@@ -163,4 +231,5 @@ driver_t driver = {
#error "Define a valid audio driver in config.h"
#endif
};
+#endif
diff --git a/driver.h b/driver.h
index d5e215da9f..f695746cd3 100644
--- a/driver.h
+++ b/driver.h
@@ -54,14 +54,19 @@ 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);
+ const char *ident;
} audio_driver_t;
+#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF)
+#define AXIS_POS_GET(x) (x & 0xFFFF)
+#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
typedef struct input_driver
{
void* (*init)(void);
void (*poll)(void* data);
int16_t (*input_state)(void* data, const struct snes_keybind **snes_keybinds, bool port, unsigned device, unsigned index, unsigned id);
void (*free)(void* data);
+ const char *ident;
} input_driver_t;
typedef struct video_driver
@@ -71,6 +76,7 @@ typedef struct video_driver
bool (*frame)(void* data, const uint16_t* frame, int width, int height, int pitch);
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding.
void (*free)(void* data);
+ const char *ident;
} video_driver_t;
@@ -92,8 +98,6 @@ void uninit_video_input(void);
void init_audio(void);
void uninit_audio(void);
-extern bool video_active;
-extern bool audio_active;
extern driver_t driver;
//////////////////////////////////////////////// Backends
@@ -102,6 +106,7 @@ extern const audio_driver_t audio_oss;
extern const audio_driver_t audio_alsa;
extern const audio_driver_t audio_roar;
extern const audio_driver_t audio_openal;
+extern const audio_driver_t audio_jack;
extern const video_driver_t video_gl;
////////////////////////////////////////////////
diff --git a/dynamic.c b/dynamic.c
new file mode 100644
index 0000000000..5c8ff14ceb
--- /dev/null
+++ b/dynamic.c
@@ -0,0 +1,142 @@
+/* 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 .
+ */
+
+#include "dynamic.h"
+#include "general.h"
+#include
+#include "config.h"
+
+#ifdef HAVE_DYNAMIC
+#include
+
+#define SYM(x) do { \
+ p##x = dlsym(lib_handle, #x); \
+ if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
+} while(0)
+
+#endif
+
+#ifdef HAVE_DYNAMIC
+static void *lib_handle = NULL;
+#endif
+
+void (*psnes_init)(void);
+
+void (*psnes_set_video_refresh)(snes_video_refresh_t);
+void (*psnes_set_audio_sample)(snes_audio_sample_t);
+void (*psnes_set_input_poll)(snes_input_poll_t);
+void (*psnes_set_input_state)(snes_input_state_t);
+
+void (*psnes_run)(void);
+
+unsigned (*psnes_library_revision_minor)(void);
+unsigned (*psnes_library_revision_major)(void);
+
+bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned);
+
+unsigned (*psnes_serialize_size)(void);
+bool (*psnes_serialize)(uint8_t*, unsigned);
+bool (*psnes_unserialize)(const uint8_t*, unsigned);
+
+void (*psnes_set_cartridge_basename)(const char*);
+
+uint8_t* (*psnes_get_memory_data)(unsigned);
+unsigned (*psnes_get_memory_size)(unsigned);
+
+void (*psnes_unload_cartridge)(void);
+void (*psnes_term)(void);
+
+#ifdef HAVE_DYNAMIC
+static void load_dynamic(void)
+{
+ SSNES_LOG("Loading dynamic libsnes from: \"%s\"\n", g_settings.libsnes);
+ lib_handle = dlopen(g_settings.libsnes, RTLD_LAZY);
+ if (!lib_handle)
+ {
+ SSNES_ERR("Failed to open dynamic library: \"%s\"\n", g_settings.libsnes);
+ exit(1);
+ }
+
+ SYM(snes_init);
+ SYM(snes_set_video_refresh);
+ SYM(snes_set_audio_sample);
+ SYM(snes_set_input_poll);
+ SYM(snes_set_input_state);
+ SYM(snes_library_revision_minor);
+ SYM(snes_library_revision_major);
+ SYM(snes_run);
+ SYM(snes_load_cartridge_normal);
+ SYM(snes_serialize_size);
+ SYM(snes_serialize);
+ SYM(snes_unserialize);
+ SYM(snes_set_cartridge_basename);
+ SYM(snes_get_memory_data);
+ SYM(snes_get_memory_size);
+ SYM(snes_unload_cartridge);
+ SYM(snes_term);
+}
+#endif
+
+#define SSYM(x) do { \
+ p##x = x; \
+} while(0)
+
+#ifndef HAVE_DYNAMIC
+static void set_statics(void)
+{
+ SSYM(snes_init);
+ SSYM(snes_set_video_refresh);
+ SSYM(snes_set_audio_sample);
+ SSYM(snes_set_input_poll);
+ SSYM(snes_set_input_state);
+ SSYM(snes_library_revision_minor);
+ SSYM(snes_library_revision_major);
+ SSYM(snes_run);
+ SSYM(snes_load_cartridge_normal);
+ SSYM(snes_serialize_size);
+ SSYM(snes_serialize);
+ SSYM(snes_unserialize);
+ SSYM(snes_set_cartridge_basename);
+ SSYM(snes_get_memory_data);
+ SSYM(snes_get_memory_size);
+ SSYM(snes_unload_cartridge);
+ SSYM(snes_term);
+}
+#endif
+
+void init_dlsym(void)
+{
+#ifdef HAVE_DYNAMIC
+ if (strlen(g_settings.libsnes) > 0)
+ load_dynamic();
+ else
+ {
+ SSNES_ERR("This binary is built to use runtime dynamic binding of libsnes. Set libsnes_path in config to load a libsnes library dynamically.\n");
+ exit(1);
+ }
+#else
+ set_statics();
+#endif
+}
+
+void uninit_dlsym(void)
+{
+#ifdef HAVE_DYNAMIC
+ if (lib_handle)
+ dlclose(lib_handle);
+#endif
+}
diff --git a/dynamic.h b/dynamic.h
new file mode 100644
index 0000000000..a804da26bc
--- /dev/null
+++ b/dynamic.h
@@ -0,0 +1,54 @@
+/* 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 __DYNAMIC_H
+#define __DYNAMIC_H
+
+#include
+#include
+
+void init_dlsym(void);
+void uninit_dlsym(void);
+
+extern void (*psnes_init)(void);
+
+extern void (*psnes_set_video_refresh)(snes_video_refresh_t);
+extern void (*psnes_set_audio_sample)(snes_audio_sample_t);
+extern void (*psnes_set_input_poll)(snes_input_poll_t);
+extern void (*psnes_set_input_state)(snes_input_state_t);
+
+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 unsigned (*psnes_serialize_size)(void);
+extern bool (*psnes_serialize)(uint8_t*, unsigned);
+extern bool (*psnes_unserialize)(const uint8_t*, unsigned);
+
+extern void (*psnes_run)(void);
+
+extern void (*psnes_set_cartridge_basename)(const char*);
+
+extern uint8_t* (*psnes_get_memory_data)(unsigned);
+extern unsigned (*psnes_get_memory_size)(unsigned);
+
+extern void (*psnes_unload_cartridge)(void);
+extern void (*psnes_term)(void);
+
+#endif
+
diff --git a/file.c b/file.c
index 6fc517592c..262eb4983e 100644
--- a/file.c
+++ b/file.c
@@ -21,6 +21,7 @@
#include
#include
#include
+#include "dynamic.h"
ssize_t read_file(FILE* file, void** buf)
{
@@ -101,7 +102,7 @@ void write_file(const char* path, uint8_t* data, size_t size)
if ( file != NULL )
{
SSNES_LOG("Saving state \"%s\". Size: %d bytes.\n", path, (int)size);
- snes_serialize(data, size);
+ psnes_serialize(data, size);
if ( fwrite(data, 1, size, file) != size )
SSNES_ERR("Did not save state properly.\n");
fclose(file);
@@ -118,7 +119,7 @@ void load_state(const char* path, uint8_t* data, size_t size)
if ( fread(data, 1, size, file) != size )
SSNES_ERR("Did not load state properly.\n");
fclose(file);
- snes_unserialize(data, size);
+ psnes_unserialize(data, size);
}
else
{
@@ -136,8 +137,8 @@ void load_save_file(const char* path, int type)
return;
}
- size_t size = snes_get_memory_size(type);
- uint8_t *data = snes_get_memory_data(type);
+ size_t size = psnes_get_memory_size(type);
+ uint8_t *data = psnes_get_memory_data(type);
if (size == 0 || !data)
{
@@ -158,8 +159,8 @@ void load_save_file(const char* path, int type)
void save_file(const char* path, int type)
{
- size_t size = snes_get_memory_size(type);
- uint8_t *data = snes_get_memory_data(type);
+ size_t size = psnes_get_memory_size(type);
+ uint8_t *data = psnes_get_memory_data(type);
if ( data && size > 0 )
write_file(path, data, size);
diff --git a/general.h b/general.h
index 50b071add8..af97152d12 100644
--- a/general.h
+++ b/general.h
@@ -21,9 +21,75 @@
#include
#include
+#include "driver.h"
+#include
+
+
+#define MAX_PLAYERS 2
+#define MAX_BINDS 14
+struct settings
+{
+ struct
+ {
+ char driver[32];
+ float xscale;
+ float yscale;
+ bool fullscreen;
+ unsigned fullscreen_x;
+ unsigned fullscreen_y;
+ bool vsync;
+ bool smooth;
+ bool force_aspect;
+ char cg_shader_path[256];
+ unsigned filter;
+ } video;
+
+ struct
+ {
+ char driver[32];
+ bool enable;
+ unsigned out_rate;
+ unsigned in_rate;
+ char device[256];
+ unsigned latency;
+ bool sync;
+ int src_quality;
+ } audio;
+
+ struct
+ {
+ 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;
+ } input;
+
+ char libsnes[256];
+};
+
+struct global
+{
+ bool verbose;
+ SRC_STATE *source;
+ bool audio_active;
+ bool video_active;
+
+ FILE *rom_file;
+ char savefile_name_srm[256];
+ char config_path[256];
+ char basename[256];
+};
+
+void parse_config(void);
+
+extern struct settings g_settings;
+extern struct global g_extern;
#define SSNES_LOG(msg, args...) do { \
- if (verbose) \
+ if (g_extern.verbose) \
fprintf(stderr, "SSNES: " msg, ##args); \
} while(0)
@@ -31,8 +97,4 @@
fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \
} while(0)
-extern bool verbose;
-extern SRC_STATE *source;
-extern bool fullscreen;
-
#endif
diff --git a/gfx/gl.c b/gfx/gl.c
index 75c0e16f01..3e9b38c351 100644
--- a/gfx/gl.c
+++ b/gfx/gl.c
@@ -18,13 +18,15 @@
#define GL_GLEXT_PROTOTYPES
#include "driver.h"
-#include "config.h"
#include
#include
#include
#include "libsnes.hpp"
#include
#include
+#include
+#include "general.h"
+#include "config.h"
#ifdef HAVE_CG
@@ -49,6 +51,7 @@ static const GLfloat tex_coords[] = {
static bool keep_aspect = true;
#ifdef HAVE_CG
static CGparameter cg_mvp_matrix;
+static bool cg_active = false;
#endif
static GLuint gl_width = 0, gl_height = 0;
typedef struct gl
@@ -65,6 +68,11 @@ typedef struct gl
#endif
GLuint texture;
GLuint tex_filter;
+
+ unsigned last_width;
+ unsigned last_height;
+ unsigned tex_w, tex_h;
+ GLfloat tex_coords[8];
} gl_t;
@@ -116,9 +124,9 @@ static bool glfw_is_pressed(int port_num, const struct snes_keybind *key, unsign
if (key->joyaxis != AXIS_NONE)
{
- if (AXIS_NEG_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_NEG_GET(key->joyaxis)] <= -AXIS_THRESHOLD)
+ if (AXIS_NEG_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_NEG_GET(key->joyaxis)] <= -g_settings.input.axis_threshold)
return true;
- if (AXIS_POS_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_POS_GET(key->joyaxis)] >= AXIS_THRESHOLD)
+ if (AXIS_POS_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_POS_GET(key->joyaxis)] >= g_settings.input.axis_threshold)
return true;
}
return false;
@@ -168,7 +176,8 @@ static void glfw_free_input(void *data)
static const input_driver_t input_glfw = {
.poll = glfw_input_poll,
.input_state = glfw_input_state,
- .free = glfw_free_input
+ .free = glfw_free_input,
+ .ident = "glfw"
};
static void GLFWCALL resize(int width, int height)
@@ -207,7 +216,8 @@ static void GLFWCALL resize(int width, int height)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
#ifdef HAVE_CG
- cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
+ if (cg_active)
+ cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
#endif
gl_width = out_width;
gl_height = out_height;
@@ -251,18 +261,43 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i
glClear(GL_COLOR_BUFFER_BIT);
#if HAVE_CG
- cgGLSetParameter2f(gl->cg_video_size, width, height);
- cgGLSetParameter2f(gl->cg_texture_size, width, height);
- cgGLSetParameter2f(gl->cg_output_size, gl_width, gl_height);
+ if (cg_active)
+ {
+ cgGLSetParameter2f(gl->cg_video_size, width, height);
+ cgGLSetParameter2f(gl->cg_texture_size, gl->tex_w, gl->tex_h);
+ cgGLSetParameter2f(gl->cg_output_size, gl_width, gl_height);
- cgGLSetParameter2f(gl->cg_Vvideo_size, width, height);
- cgGLSetParameter2f(gl->cg_Vtexture_size, width, height);
- cgGLSetParameter2f(gl->cg_Voutput_size, gl_width, gl_height);
+ cgGLSetParameter2f(gl->cg_Vvideo_size, width, height);
+ cgGLSetParameter2f(gl->cg_Vtexture_size, gl->tex_w, gl->tex_h);
+ cgGLSetParameter2f(gl->cg_Voutput_size, gl_width, gl_height);
+ }
#endif
+ 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);
+ 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,
+ GL_UNSIGNED_SHORT_1_5_5_5_REV, tmp);
+ free(tmp);
+
+ gl->tex_coords[0] = 0;
+ gl->tex_coords[1] = (GLfloat)height / gl->tex_h;
+ gl->tex_coords[2] = 0;
+ gl->tex_coords[3] = 0;
+ gl->tex_coords[4] = (GLfloat)width / gl->tex_w;
+ gl->tex_coords[5] = 0;
+ gl->tex_coords[6] = (GLfloat)width / gl->tex_w;
+ gl->tex_coords[7] = (GLfloat)height / gl->tex_h;
+ }
+
+
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> 1);
- glTexImage2D(GL_TEXTURE_2D,
- 0, GL_RGBA, width, height, 0, GL_BGRA,
+ glTexSubImage2D(GL_TEXTURE_2D,
+ 0, 0, 0, width, height, GL_BGRA,
GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
glDrawArrays(GL_QUADS, 0, 4);
@@ -276,7 +311,8 @@ static void gl_free(void *data)
{
gl_t *gl = data;
#ifdef HAVE_CG
- cgDestroyContext(gl->cgCtx);
+ if (cg_active)
+ cgDestroyContext(gl->cgCtx);
#endif
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
@@ -298,7 +334,7 @@ static void gl_set_nonblock_state(void *data, bool state)
static void* gl_init(video_info_t *video, const input_driver_t **input)
{
- gl_t *gl = malloc(sizeof(gl_t));
+ gl_t *gl = calloc(1, sizeof(gl_t));
if ( gl == NULL )
return NULL;
@@ -350,47 +386,65 @@ static void* gl_init(video_info_t *video, const input_driver_t **input)
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_FLOAT, 3 * sizeof(GLfloat), vertexes);
- glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), tex_coords);
+
+ memcpy(gl->tex_coords, tex_coords, sizeof(tex_coords));
+ glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
+
+ gl->tex_w = 256 * video->input_scale;
+ gl->tex_h = 256 * video->input_scale;
+ uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint16_t));
+ glTexImage2D(GL_TEXTURE_2D,
+ 0, GL_RGBA, gl->tex_w, gl->tex_h, 0, GL_BGRA,
+ GL_UNSIGNED_SHORT_1_5_5_5_REV, tmp);
+ free(tmp);
+ gl->last_width = gl->tex_w;
+ gl->last_height = gl->tex_h;
#ifdef HAVE_CG
- gl->cgCtx = cgCreateContext();
- if (gl->cgCtx == NULL)
+ cg_active = false;
+ if (strlen(g_settings.video.cg_shader_path) > 0)
{
- fprintf(stderr, "Failed to create Cg context\n");
- goto error;
- }
- gl->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
- gl->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);
- if (gl->cgFProf == CG_PROFILE_UNKNOWN || gl->cgVProf == CG_PROFILE_UNKNOWN)
- {
- fprintf(stderr, "Invalid profile type\n");
- goto error;
- }
- cgGLSetOptimalOptions(gl->cgFProf);
- cgGLSetOptimalOptions(gl->cgVProf);
- gl->cgFPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, cg_shader_path, gl->cgFProf, "main_fragment", 0);
- gl->cgVPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, cg_shader_path, gl->cgVProf, "main_vertex", 0);
- if (gl->cgFPrg == NULL || gl->cgVPrg == NULL)
- {
- CGerror err = cgGetError();
- fprintf(stderr, "CG error: %s\n", cgGetErrorString(err));
- goto error;
- }
- cgGLLoadProgram(gl->cgFPrg);
- cgGLLoadProgram(gl->cgVPrg);
- cgGLEnableProfile(gl->cgFProf);
- cgGLEnableProfile(gl->cgVProf);
- cgGLBindProgram(gl->cgFPrg);
- cgGLBindProgram(gl->cgVPrg);
+ SSNES_LOG("Loading Cg file: %s\n", g_settings.video.cg_shader_path);
+ gl->cgCtx = cgCreateContext();
+ if (gl->cgCtx == NULL)
+ {
+ fprintf(stderr, "Failed to create Cg context\n");
+ goto error;
+ }
+ gl->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
+ gl->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);
+ if (gl->cgFProf == CG_PROFILE_UNKNOWN || gl->cgVProf == CG_PROFILE_UNKNOWN)
+ {
+ fprintf(stderr, "Invalid profile type\n");
+ goto error;
+ }
+ cgGLSetOptimalOptions(gl->cgFProf);
+ cgGLSetOptimalOptions(gl->cgVProf);
+ gl->cgFPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgFProf, "main_fragment", 0);
+ gl->cgVPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgVProf, "main_vertex", 0);
+ if (gl->cgFPrg == NULL || gl->cgVPrg == NULL)
+ {
+ CGerror err = cgGetError();
+ fprintf(stderr, "CG error: %s\n", cgGetErrorString(err));
+ goto error;
+ }
+ cgGLLoadProgram(gl->cgFPrg);
+ cgGLLoadProgram(gl->cgVPrg);
+ cgGLEnableProfile(gl->cgFProf);
+ cgGLEnableProfile(gl->cgVProf);
+ cgGLBindProgram(gl->cgFPrg);
+ cgGLBindProgram(gl->cgVPrg);
- gl->cg_video_size = cgGetNamedParameter(gl->cgFPrg, "IN.video_size");
- gl->cg_texture_size = cgGetNamedParameter(gl->cgFPrg, "IN.texture_size");
- gl->cg_output_size = cgGetNamedParameter(gl->cgFPrg, "IN.output_size");
- gl->cg_Vvideo_size = cgGetNamedParameter(gl->cgVPrg, "IN.video_size");
- gl->cg_Vtexture_size = cgGetNamedParameter(gl->cgVPrg, "IN.texture_size");
- gl->cg_Voutput_size = cgGetNamedParameter(gl->cgVPrg, "IN.output_size");
- cg_mvp_matrix = cgGetNamedParameter(gl->cgVPrg, "modelViewProj");
- cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
+ gl->cg_video_size = cgGetNamedParameter(gl->cgFPrg, "IN.video_size");
+ gl->cg_texture_size = cgGetNamedParameter(gl->cgFPrg, "IN.texture_size");
+ gl->cg_output_size = cgGetNamedParameter(gl->cgFPrg, "IN.output_size");
+ gl->cg_Vvideo_size = cgGetNamedParameter(gl->cgVPrg, "IN.video_size");
+ gl->cg_Vtexture_size = cgGetNamedParameter(gl->cgVPrg, "IN.texture_size");
+ gl->cg_Voutput_size = cgGetNamedParameter(gl->cgVPrg, "IN.output_size");
+ cg_mvp_matrix = cgGetNamedParameter(gl->cgVPrg, "modelViewProj");
+ cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
+ cg_active = true;
+ }
#endif
*input = &input_glfw;
@@ -406,7 +460,8 @@ const video_driver_t video_gl = {
.init = gl_init,
.frame = gl_frame,
.set_nonblock_state = gl_set_nonblock_state,
- .free = gl_free
+ .free = gl_free,
+ .ident = "glfw"
};
diff --git a/hqflt/cg/crt.cg b/hqflt/cg/crt.cg
index ab89abc478..bee618ad38 100644
--- a/hqflt/cg/crt.cg
+++ b/hqflt/cg/crt.cg
@@ -52,7 +52,7 @@ output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXU
float2 rubyTextureSize = IN.texture_size;
float2 xy = barrelDistortion(texCoord.xy);
- float2 one = 0.999/rubyTextureSize;
+ float2 one = 1.0/rubyTextureSize;
xy = xy + float2(0.0 , -0.5 * (phase + (1-phase) * rubyInputSize.y/rubyOutputSize.y) * one.y);
float4 texels[8];
texels[0] = TEX2D(xy + float2(-one.x,0.0));
diff --git a/hqflt/filters.h b/hqflt/filters.h
new file mode 100644
index 0000000000..439b3f3727
--- /dev/null
+++ b/hqflt/filters.h
@@ -0,0 +1,43 @@
+/* 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 __FILTERS_H
+#define __FILTERS_H
+
+#include "config.h"
+
+#ifdef HAVE_FILTER
+
+#include "pastlib.h"
+#include "grayscale.h"
+#include "bleed.h"
+#include "ntsc.h"
+
+#define FILTER_HQ2X 1
+#define FILTER_HQ4X 2
+#define FILTER_GRAYSCALE 3
+#define FILTER_BLEED 4
+#define FILTER_NTSC 5
+#define FILTER_HQ2X_STR "hq2x"
+#define FILTER_HQ4X_STR "hq4x"
+#define FILTER_GRAYSCALE_STR "grayscale"
+#define FILTER_BLEED_STR "bleed"
+#define FILTER_NTSC_STR "ntsc"
+#endif
+
+#endif
diff --git a/qb/conf.comp.sh b/qb/conf.comp.sh
new file mode 100644
index 0000000000..cd894adfb6
--- /dev/null
+++ b/qb/conf.comp.sh
@@ -0,0 +1,2 @@
+USE_LANG_C="yes"
+USE_LANG_CXX="yes"
diff --git a/qb/config.libs.sh b/qb/config.libs.sh
new file mode 100644
index 0000000000..a01eaf03d9
--- /dev/null
+++ b/qb/config.libs.sh
@@ -0,0 +1,32 @@
+. qb/qb.libs.sh
+
+check_switch_c C99 -std=gnu99
+check_critical C99 "Cannot find C99 compatible compiler."
+
+if [ $HAVE_DYNAMIC != yes ]; then
+ check_lib_cxx SNES $LIBSNES snes_init -ldl
+ check_critical SNES "Cannot find libsnes."
+ add_define_make libsnes $LIBSNES
+fi
+
+check_lib ALSA -lasound snd_pcm_open
+check_header OSS sys/soundcard.h
+check_lib AL -lopenal alcOpenDevice
+check_lib RSOUND -lrsound rsd_init
+check_lib ROAR -lroar roar_vs_new
+check_lib JACK -ljack jack_client_open
+
+check_lib GLFW -lglfw glfwInit
+check_critical GLFW "Cannot find GLFW library."
+
+check_lib CG -lCg cgCreateContext
+
+check_lib SRC -lsamplerate src_callback_new
+
+check_lib DYNAMIC -ldl dlopen
+
+# Creates config.mk.
+VARS="ALSA OSS AL RSOUND ROAR JACK GLFW FILTER CG DYNAMIC"
+create_config_make config.mk $VARS
+create_config_header config.h $VARS
+
diff --git a/qb/config.params.sh b/qb/config.params.sh
new file mode 100644
index 0000000000..34dc9a88d2
--- /dev/null
+++ b/qb/config.params.sh
@@ -0,0 +1,19 @@
+. qb/qb.params.sh
+
+PACKAGE_NAME=ssnes
+PACKAGE_VERSION=0.1
+
+# Adds a command line opt to ./configure --help
+# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
+# $2: Comment
+# $3: Default arg. auto implies that HAVE_ALSA will be set according to library checks later on.
+add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library." no
+add_command_line_string LIBSNES "libsnes library used" "-lsnes"
+add_command_line_enable FILTER "Disable CPU filter support" yes
+add_command_line_enable CG "Enable CG shader support" auto
+add_command_line_enable ALSA "Enable ALSA support" auto
+add_command_line_enable OSS "Enable OSS support" auto
+add_command_line_enable RSOUND "Enable RSound support" auto
+add_command_line_enable ROAR "Enable RoarAudio support" auto
+add_command_line_enable AL "Enable OpenAL support" auto
+add_command_line_enable JACK "Enable JACK support" auto
diff --git a/qb/qb.comp.sh b/qb/qb.comp.sh
new file mode 100644
index 0000000000..12c75a6074
--- /dev/null
+++ b/qb/qb.comp.sh
@@ -0,0 +1,68 @@
+. qb/conf.comp.sh
+
+TEMP_C=.tmp.c
+TEMP_CXX=.tmp.cxx
+TEMP_EXE=.tmp
+
+echo -n "Checking operating system ... "
+OS="Win32" # whatever ;D
+unamestr="`uname -o`"
+if [ ! -z "`echo $unamestr | grep -i Linux`" ]; then
+ OS="Linux"
+elif [ ! -z "`echo $unamestr | grep -i Darwin`" ]; then
+ OS="Darwin"
+elif [ ! -z "`echo $unamestr | grep -i BSD`" ]; then
+ OS="BSD"
+elif [ ! -z "`echo $unamestr | grep -i NT`" ]; then
+ OS="Cygwin"
+fi
+
+echo $OS
+
+
+# Checking for working C compiler
+if [ "$USE_LANG_C" = yes ]; then
+ echo "Checking for working C compiler ..."
+ if [ -z $CC ]; then
+ CC=`which gcc cc 2> /dev/null | grep ^/ | head -n 1`
+ fi
+ if [ -z $CC ]; then
+ echo "Could not find C compiler in path. Exiting ..."
+ exit 1
+ fi
+
+ echo -n "Checking if $CC is a suitable compiler ... "
+ answer=no
+ echo "#include " > $TEMP_C
+ echo "int main(void) { puts(\"Hai world!\"); return 0; }" >> $TEMP_C
+ $CC -o $TEMP_EXE $TEMP_C 2>/dev/null >/dev/null && answer=yes
+ echo $answer
+
+ rm -rf $TEMP_C $TEMP_EXE
+
+ [ $answer = no ] && echo "Can't find suitable C compiler. Exiting ..." && exit 1
+fi
+
+# Checking for working C++ compiler
+if [ "$USE_LANG_CXX" = "yes" ]; then
+ echo "Checking for working C++ compiler ..."
+ if [ -z $CXX ]; then
+ CXX=`which g++ c++ 2> /dev/null | grep ^/ | head -n 1`
+ fi
+ if [ -z $CXX ]; then
+ echo "Could not find C compiler in path. Exiting ..."
+ exit 1
+ fi
+
+ echo -n "Checking if $CXX is a suitable compiler ... "
+ answer=no
+ echo "#include " > $TEMP_CXX
+ echo "int main() { std::cout << \"Hai guise\" << std::endl; return 0; }" >> $TEMP_CXX
+ $CXX -o $TEMP_EXE $TEMP_CXX 2>/dev/null >/dev/null && answer=yes
+ echo $answer
+
+ rm -rf $TEMP_CXX $TEMP_EXE
+
+ [ $answer = no ] && echo "Can't find suitable C++ compiler. Exiting ..." && exit 1
+fi
+
diff --git a/qb/qb.libs.sh b/qb/qb.libs.sh
new file mode 100644
index 0000000000..ab8585fc83
--- /dev/null
+++ b/qb/qb.libs.sh
@@ -0,0 +1,313 @@
+
+PKG_CONF_PATH=""
+PKG_CONF_USED=""
+CONFIG_DEFINES=""
+MAKEFILE_DEFINES=""
+INCLUDE_DIRS=""
+LIBRARY_DIRS=""
+[ -z "$PREFIX" ] && PREFIX="/usr/local"
+
+add_define_header()
+{
+ CONFIG_DEFINES="$CONFIG_DEFINES:@$1@$2@:"
+}
+
+add_define_make()
+{
+ MAKEFILE_DEFINES="$MAKEFILE_DEFINES:@$1@$2@:"
+}
+
+add_include_dirs()
+{
+ while [ ! -z "$1" ]
+ do
+ INCLUDE_DIRS="$INCLUDE_DIRS -I$1"
+ shift
+ done
+}
+
+add_library_dirs()
+{
+ while [ ! -z "$1" ]
+ do
+ LIBRARY_DIRS="$LIBRARY_DIRS -L$1"
+ shift
+ done
+}
+
+check_lib()
+{
+ tmpval="HAVE_$1"
+ eval tmpval=\$$tmpval
+ [ "$tmpval" = "no" ] && return 0
+
+ echo -n "Checking function $3 in $2 ... "
+ echo "void $3(void); int main(void) { $3(); return 0; }" > $TEMP_C
+
+
+ eval HAVE_$1=no
+ answer=no
+
+ extralibs="$4"
+
+ $CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
+
+ echo $answer
+
+ rm -rf $TEMP_C $TEMP_EXE
+ if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
+ echo "Forced to build with library $2, but cannot locate. Exiting ..."
+ exit 1
+ fi
+}
+
+check_lib_cxx()
+{
+ tmpval="HAVE_$1"
+ eval tmpval=\$$tmpval
+ [ "$tmpval" = "no" ] && return 0
+
+ echo -n "Checking function $3 in $2 ... "
+ echo "extern \"C\" { void $3(void); } int main() { $3(); }" > $TEMP_CXX
+
+ eval HAVE_$1=no
+ answer=no
+
+ extralibs="$4"
+
+ $CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
+
+ echo $answer
+
+ rm -rf $TEMP_CXX $TEMP_EXE
+ if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
+ echo "Forced to build with library $2, but cannot locate. Exiting ..."
+ exit 1
+ fi
+}
+
+locate_pkg_conf()
+{
+ echo -n "Checking for pkg-config ... "
+ PKG_CONF_PATH="`which pkg-config | grep ^/ | head -n1`"
+ if [ -z $PKG_CONF_PATH ]; then
+ echo "not found"
+ echo "Cannot locate pkg-config. Exiting ..."
+ exit 1
+ fi
+ echo "$PKG_CONF_PATH"
+}
+
+check_pkgconf()
+{
+ [ -z "$PKG_CONF_PATH" ] && locate_pkg_conf
+
+ tmpval="HAVE_$1"
+ eval tmpval=\$$tmpval
+ [ "$tmpval" = "no" ] && return 0
+
+ echo -n "Checking presence of package $2 ... "
+ eval HAVE_$1=no
+ eval $1_CFLAGS=""
+ eval $1_LIBS=""
+ answer=no
+ minver=0.0
+ [ ! -z $3 ] && minver=$3
+ pkg-config --atleast-version=$minver --exists "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes
+ echo $answer
+
+ PKG_CONF_USED="$PKG_CONF_USED $1"
+
+ if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
+ echo "Forced to build with package $2, but cannot locate. Exiting ..."
+ exit 1
+ fi
+}
+
+check_header()
+{
+ tmpval="HAVE_$1"
+ eval tmpval=\$$tmpval
+ [ "$tmpval" = "no" ] && return 0
+
+ echo -n "Checking presence of header file $2 ... "
+ echo "#include<$2>" > $TEMP_C
+ echo "int main(void) { return 0; }" >> $TEMP_C
+ eval HAVE_$1=no
+ answer=no
+
+ $CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
+
+ echo $answer
+
+ rm -rf $TEMP_C $TEMP_EXE
+ if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
+ echo "Build assumed that $2 exists, but cannot locate. Exiting ..."
+ exit 1
+ fi
+}
+
+check_switch_c()
+{
+ echo -n "Checking for availability of switch $2 in $CC ... "
+ if [ -z "$CC" ]; then
+ echo "No C compiler, cannot check ..."
+ exit 1
+ fi
+ echo "int main(void) { return 0; }" > $TEMP_C
+ eval HAVE_$1=no
+ answer=no
+ $CC -o $TEMP_EXE $TEMP_C $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
+
+ echo $answer
+
+ rm -rf $TEMP_C $TEMP_EXE
+}
+
+check_switch_cxx()
+{
+ echo -n "Checking for availability of switch $2 in $CXX ... "
+ if [ -z "$CXX" ]; then
+ echo "No C++ compiler, cannot check ..."
+ exit 1
+ fi
+ echo "int main() { return 0; }" > $TEMP_CXX
+ eval HAVE_$1=no
+ answer=no
+ $CXX -o $TEMP_EXE $TEMP_CXX $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
+
+ echo $answer
+
+ rm -rf $TEMP_CXX $TEMP_EXE
+}
+
+check_critical()
+{
+ val=HAVE_$1
+ eval val=\$$val
+ if [ "$val" != "yes" ]; then
+ echo "$2"
+ exit 1
+ fi
+}
+
+output_define_header()
+{
+ arg1="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\1|'`"
+ arg2="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\2|'`"
+
+ echo "#define $arg1 $arg2" >> "$outfile"
+}
+
+create_config_header()
+{
+ outfile="$1"
+ shift
+
+ echo "Creating config header: $outfile"
+
+ name="`echo __$outfile | sed 's|[\./]|_|g' | tr '[a-z]' '[A-Z]'`"
+ echo "#ifndef $name" > "$outfile"
+ echo "#define $name" >> "$outfile"
+ echo "" >> "$outfile"
+ echo "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >> "$outfile"
+ echo "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >> "$outfile"
+
+ while [ ! -z "$1" ]
+ do
+ tmpval="HAVE_$1"
+ eval tmpval=\$$tmpval
+ if [ "$tmpval" = "yes" ]; then
+ echo "#define HAVE_$1 1" >> "$outfile"
+ elif [ "$tmpval" = "no" ]; then
+ echo "/* #undef HAVE_$1 */" >> "$outfile"
+ fi
+
+ shift
+ done
+
+ echo "" >> "$outfile"
+
+ tmpdefs="$CONFIG_DEFINES"
+ while [ ! -z "$tmpdefs" ]
+ do
+ subdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):.*$|\1|'`"
+ tmpdefs="`echo $tmpdefs | sed 's|^\W*$||'`"
+ tmpdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):||'`"
+ output_define_header "$outfile" "$subdefs"
+ done
+
+ echo "#endif" >> "$outfile"
+}
+
+output_define_make()
+{
+ arg1="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\1|'`"
+ arg2="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\2|'`"
+
+ echo "$arg1 = $arg2" >> "$outfile"
+}
+
+create_config_make()
+{
+
+ outfile="$1"
+ shift
+
+ echo "Creating make config: $outfile"
+
+ rm -rf "$outfile"
+ touch "$outfile"
+ if [ "$USE_LANG_C" = "yes" ]; then
+ echo "CC = $CC" >> "$outfile"
+ echo "CFLAGS = $CFLAGS" >> "$outfile"
+ fi
+ if [ "$USE_LANG_CXX" = "yes" ]; then
+ echo "CXX = $CXX" >> "$outfile"
+ echo "CXXFLAGS = $CXXFLAGS" >> "$outfile"
+ fi
+ echo "LDFLAGS = $LDFLAGS" >> "$outfile"
+ echo "INCLUDE_DIRS = $INCLUDE_DIRS" >> "$outfile"
+ echo "LIBRARY_DIRS = $LIBRARY_DIRS" >> "$outfile"
+ echo "PACKAGE_NAME = $PACKAGE_NAME" >> "$outfile"
+ echo "PACKAGE_VERSION = $PACKAGE_VERSION" >> "$outfile"
+ echo "PREFIX = $PREFIX" >> "$outfile"
+
+ while [ ! -z "$1" ]
+ do
+ tmpval="HAVE_$1"
+ eval tmpval=\$$tmpval
+ if [ "$tmpval" = yes ]; then
+ echo "HAVE_$1 = 1" >> "$outfile"
+ elif [ "$tmpval" = no ]; then
+ echo "HAVE_$1 = 0" >> "$outfile"
+ fi
+
+ if [ ! -z "`echo $PKG_CONF_USED | grep $1`" ]; then
+ tmpval="$1_CFLAGS"
+ eval tmpval=\$$tmpval
+ echo "$1_CFLAGS = $tmpval" >> "$outfile"
+
+ tmpval="$1_LIBS"
+ eval tmpval=\$$tmpval
+ echo "$1_LIBS = $tmpval" >> "$outfile"
+ fi
+
+
+ shift
+ done
+
+ echo "" >> "$outfile"
+
+ tmpdefs="$MAKEFILE_DEFINES"
+ while [ ! -z "$tmpdefs" ]
+ do
+ subdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):.*$|\1|'`"
+ tmpdefs="`echo $tmpdefs | sed 's|^\W*$||'`"
+ tmpdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):||'`"
+ output_define_make "$outfile" "$subdefs"
+ done
+
+}
+
+
diff --git a/qb/qb.params.sh b/qb/qb.params.sh
new file mode 100644
index 0000000000..231310d8e5
--- /dev/null
+++ b/qb/qb.params.sh
@@ -0,0 +1,150 @@
+COMMAND_LINE_OPTS_ENABLE=""
+
+add_command_line_enable()
+{
+ COMMAND_LINE_OPTS_ENABLE="$COMMAND_LINE_OPTS_ENABLE:\"$1\" \"$2\" \"$3\":"
+ eval HAVE_$1=$3
+}
+
+add_command_line_string()
+{
+ COMMAND_LINE_OPTS_STRINGS="$COMMAND_LINE_OPTS_STRINGS:\"$1\" \"$2\" \"$3\":"
+ eval $1=$3
+}
+
+## lvl. 43 regex dragon awaits thee.
+print_help()
+{
+ echo "===================="
+ echo " Quickbuild script"
+ echo "===================="
+ echo "Package: $PACKAGE_NAME"
+ echo "Version: $PACKAGE_VERSION"
+ echo ""
+ echo "General environment variables:"
+ echo "CC: C compiler"
+ echo "CFLAGS: C compiler flags"
+ echo "CXX: C++ compiler"
+ echo "CXXFLAGS: C++ compiler flags"
+ echo "LDFLAGS: Linker flags"
+ echo ""
+ echo "General options:"
+ echo "--prefix=\$path: Install path prefix"
+ echo "--help: Show this help"
+ echo ""
+ echo "Custom options:"
+
+ tmpopts="$COMMAND_LINE_OPTS_ENABLE"
+ while [ ! -z "$tmpopts" ]
+ do
+ subopts="`echo $tmpopts | sed 's|^:"\([^"]*\)"."\([^"]*\)"."\([^"]*\)":.*$|"\1":"\2":"\3"|'`"
+ tmpopts="`echo $tmpopts | sed 's|^\W*$||'`"
+ tmpopts="`echo $tmpopts | sed 's|^:"[^"]*"."[^"]*"."[^"]*":||'`"
+ print_sub_opt "$subopts"
+ done
+
+ echo ""
+
+ tmpopts="$COMMAND_LINE_OPTS_STRINGS"
+ while [ ! -z "$tmpopts" ]
+ do
+ subopts="`echo $tmpopts | sed 's|^:"\([^"]*\)"."\([^"]*\)"."\([^"]*\)":.*$|"\1":"\2":"\3"|'`"
+ tmpopts="`echo $tmpopts | sed 's|^\W*$||'`"
+ tmpopts="`echo $tmpopts | sed 's|^:"[^"]*"."[^"]*"."[^"]*":||'`"
+ print_sub_str_opt "$subopts"
+ done
+}
+
+print_sub_opt()
+{
+ arg1="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\1|'`"
+ arg2="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\2|'`"
+ arg3="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\3|'`"
+
+ lowertext="`echo $arg1 | tr '[A-Z]' '[a-z]'`"
+
+ if [ "$arg3" = "auto" ]; then
+ echo -n "--enable-$lowertext: "
+ echo $arg2
+ echo "--disable-$lowertext"
+ elif [ "$arg3" = "yes" ]; then
+ echo "--disable-$lowertext: $arg2"
+ elif [ "$arg3" = "no" ]; then
+ echo "--enable-$lowertext: $arg2"
+ fi
+}
+
+print_sub_str_opt()
+{
+ arg1="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\1|'`"
+ arg2="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\2|'`"
+ arg3="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\3|'`"
+
+ lowertext="`echo $arg1 | tr '[A-Z]' '[a-z]'`"
+
+ echo "--with-$lowertext: $arg2 (Defaults: $arg3)"
+}
+
+parse_input()
+{
+ ### Parse stuff :V
+
+ while [ ! -z "$1" ]
+ do
+
+ case "$1" in
+
+ --prefix=*)
+ prefix="`echo $1 | sed -e 's|^--prefix=\(\S\S*\)$|\1|' -e 's|\(\S\S*\)/$|\1|'`"
+
+ if [ "$prefix" != "$1" ]; then
+ PREFIX="$prefix"
+ fi
+ ;;
+
+ --enable-*)
+ enable=`echo $1 | sed 's|^--enable-||'`
+ if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $enable`" ]; then
+ print_help
+ exit 1
+ fi
+ eval HAVE_`echo $enable | tr '[a-z]' '[A-Z]'`=yes
+ ;;
+
+ --disable-*)
+ disable=`echo $1 | sed 's|^--disable-||'`
+ if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $disable`" ]; then
+ print_help
+ exit 1
+ fi
+ eval HAVE_`echo $disable | tr '[a-z]' '[A-Z]'`=no
+ ;;
+
+ --with-*)
+ arg="`echo $1 | sed 's|^--with-\S\S*=||'`"
+ with=`echo $1 | sed 's|^--with-\(\S\S*\)=.*$|\1|'`
+ if [ -z "`echo $COMMAND_LINE_OPTS_STRINGS | grep -i $with`" ]; then
+ print_help
+ exit 1
+ fi
+ eval "`echo $with | tr '[a-z]' '[A-Z]'`=\"$arg\""
+ ;;
+
+
+ -h|--help)
+ print_help
+ exit 0
+ ;;
+ *)
+ print_help
+ exit 1
+ ;;
+
+ esac
+
+ shift
+
+ done
+}
+
+
diff --git a/settings.c b/settings.c
new file mode 100644
index 0000000000..0288dd3dd6
--- /dev/null
+++ b/settings.c
@@ -0,0 +1,461 @@
+/* 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 .
+ */
+
+#include "general.h"
+#include "conf/config_file.h"
+#include "config.def.h"
+#include
+#include
+#include "hqflt/filters.h"
+#include "config.h"
+#include
+
+struct settings g_settings;
+
+static void read_keybinds(config_file_t *conf);
+
+static void set_defaults(void)
+{
+ const char *def_video = NULL;
+ const char *def_audio = NULL;
+
+ switch (VIDEO_DEFAULT_DRIVER)
+ {
+ case VIDEO_GL:
+ def_video = "glfw";
+ break;
+ default:
+ break;
+ }
+
+ switch (AUDIO_DEFAULT_DRIVER)
+ {
+ case AUDIO_RSOUND:
+ def_audio = "rsound";
+ break;
+ case AUDIO_OSS:
+ def_audio = "oss";
+ break;
+ case AUDIO_ALSA:
+ def_audio = "alsa";
+ break;
+ case AUDIO_ROAR:
+ def_audio = "roar";
+ break;
+ case AUDIO_AL:
+ def_audio = "openal";
+ break;
+ default:
+ break;
+ }
+
+ // No input atm ... It is in the GLFW driver.
+
+ if (def_video)
+ strncpy(g_settings.video.driver, def_video, sizeof(g_settings.video.driver) - 1);
+ if (def_audio)
+ strncpy(g_settings.audio.driver, def_audio, sizeof(g_settings.audio.driver) - 1);
+
+ g_settings.video.xscale = xscale;
+ g_settings.video.yscale = yscale;
+ g_settings.video.fullscreen = fullscreen;
+ g_settings.video.fullscreen_x = fullscreen_x;
+ g_settings.video.fullscreen_y = fullscreen_y;
+ g_settings.video.vsync = vsync;
+ g_settings.video.smooth = video_smooth;
+ g_settings.video.force_aspect = force_aspect;
+
+ g_settings.audio.enable = audio_enable;
+ g_settings.audio.out_rate = out_rate;
+ g_settings.audio.in_rate = in_rate;
+ if (audio_device)
+ strncpy(g_settings.audio.device, audio_device, sizeof(g_settings.audio.device));
+ g_settings.audio.latency = out_latency;
+ g_settings.audio.sync = audio_sync;
+ g_settings.audio.src_quality = SAMPLERATE_QUALITY;
+
+ assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1));
+ assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2));
+ 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));
+
+ 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 = GLFW_KEY_ESC;
+}
+
+void parse_config(void)
+{
+ memset(&g_settings, 0, sizeof(struct settings));
+ config_file_t *conf = NULL;
+
+ if (strlen(g_extern.config_path) > 0)
+ {
+ conf = config_file_new(g_extern.config_path);
+ if (!conf)
+ {
+ SSNES_ERR("Couldn't find config at path: \"%s\"\n", g_extern.config_path);
+ exit(1);
+ }
+ }
+ else
+ {
+ const char *xdg = getenv("XDG_CONFIG_HOME");
+ const char *home = getenv("HOME");
+ if (xdg)
+ {
+ char conf_path[strlen(xdg) + strlen("/ssnes/ssnes.cfg ")];
+ strcpy(conf_path, xdg);
+ strcat(conf_path, "/ssnes/ssnes.cfg");
+ conf = config_file_new(conf_path);
+ }
+ else if (home)
+ {
+ char conf_path[strlen(home) + strlen("/.ssnesrc ")];
+ strcpy(conf_path, xdg);
+ strcat(conf_path, "/.ssnesrc");
+ conf = config_file_new(conf_path);
+ }
+ // Try this as a last chance...
+ if (!conf)
+ conf = config_file_new("/etc/ssnes.cfg");
+ }
+
+ set_defaults();
+ if (conf == NULL)
+ return;
+
+ int tmp_int;
+ double tmp_double;
+ bool tmp_bool;
+ char *tmp_str;
+
+ // Video settings.
+ if (config_get_double(conf, "video_xscale", &tmp_double))
+ g_settings.video.xscale = tmp_double;
+
+ if (config_get_double(conf, "video_yscale", &tmp_double))
+ g_settings.video.yscale = tmp_double;
+
+ if (config_get_int(conf, "video_fullscreen_x", &tmp_int))
+ g_settings.video.fullscreen_x = tmp_int;
+
+ if (config_get_int(conf, "video_fullscreen_y", &tmp_int))
+ g_settings.video.fullscreen_y = tmp_int;
+
+ if (config_get_bool(conf, "video_fullscreen", &tmp_bool))
+ g_settings.video.fullscreen = tmp_bool;
+
+ if (config_get_bool(conf, "video_vsync", &tmp_bool))
+ g_settings.video.vsync = tmp_bool;
+
+ if (config_get_bool(conf, "video_smooth", &tmp_bool))
+ g_settings.video.smooth = tmp_bool;
+
+ if (config_get_bool(conf, "video_force_aspect", &tmp_bool))
+ g_settings.video.force_aspect = tmp_bool;
+
+ if (config_get_string(conf, "video_cg_shader", &tmp_str))
+ {
+ strncpy(g_settings.video.cg_shader_path, tmp_str, sizeof(g_settings.video.cg_shader_path) - 1);
+ free(tmp_str);
+ }
+
+#ifdef HAVE_FILTER
+ if (config_get_string(conf, "video_filter", &tmp_str))
+ {
+ unsigned filter = 0;
+ if (strcasecmp(FILTER_HQ2X_STR, tmp_str) == 0)
+ filter = FILTER_HQ2X;
+ else if (strcasecmp(FILTER_HQ4X_STR, tmp_str) == 0)
+ filter = FILTER_HQ4X;
+ else if (strcasecmp(FILTER_GRAYSCALE_STR, tmp_str) == 0)
+ filter = FILTER_GRAYSCALE;
+ else if (strcasecmp(FILTER_BLEED_STR, tmp_str) == 0)
+ filter = FILTER_BLEED;
+ else if (strcasecmp(FILTER_NTSC_STR, tmp_str) == 0)
+ filter = FILTER_NTSC;
+ else
+ {
+ SSNES_ERR(
+ "Invalid filter... Valid filters are:\n"
+ "\t%s\n"
+ "\t%s\n"
+ "\t%s\n"
+ "\t%s\n"
+ "\t%s\n",
+ FILTER_HQ2X_STR, FILTER_HQ4X_STR, FILTER_GRAYSCALE_STR,
+ FILTER_BLEED_STR, FILTER_NTSC_STR);
+ exit(1);
+ }
+
+ free(tmp_str);
+ g_settings.video.filter = filter;
+ }
+#endif
+
+ // Input Settings.
+ if (config_get_double(conf, "input_axis_threshold", &tmp_double))
+ g_settings.input.axis_threshold = tmp_double;
+
+ // Audio settings.
+ if (config_get_bool(conf, "audio_enable", &tmp_bool))
+ g_settings.audio.enable = tmp_bool;
+
+ if (config_get_int(conf, "audio_out_rate", &tmp_int))
+ g_settings.audio.out_rate = tmp_int;
+
+ if (config_get_int(conf, "audio_in_rate", &tmp_int))
+ g_settings.audio.in_rate = tmp_int;
+
+ if (config_get_string(conf, "audio_device", &tmp_str))
+ {
+ strncpy(g_settings.audio.device, tmp_str, sizeof(g_settings.audio.device) - 1);
+ free(tmp_str);
+ }
+
+ if (config_get_int(conf, "audio_latency", &tmp_int))
+ g_settings.audio.latency = tmp_int;
+
+ if (config_get_bool(conf, "audio_sync", &tmp_bool))
+ g_settings.audio.sync = tmp_bool;
+
+ if (config_get_int(conf, "audio_src_quality", &tmp_int))
+ {
+ int quals[] = { SRC_ZERO_ORDER_HOLD, SRC_LINEAR, SRC_SINC_FASTEST,
+ SRC_SINC_MEDIUM_QUALITY, SRC_SINC_BEST_QUALITY };
+
+ if (tmp_int > 0 && tmp_int < 6)
+ g_settings.audio.src_quality = quals[tmp_int];
+ }
+
+ if (config_get_string(conf, "video_driver", &tmp_str))
+ {
+ strncpy(g_settings.video.driver, tmp_str, sizeof(g_settings.video.driver) - 1);
+ free(tmp_str);
+ }
+ if (config_get_string(conf, "audio_driver", &tmp_str))
+ {
+ strncpy(g_settings.audio.driver, tmp_str, sizeof(g_settings.audio.driver) - 1);
+ free(tmp_str);
+ }
+ if (config_get_string(conf, "libsnes_path", &tmp_str))
+ {
+ strncpy(g_settings.libsnes, tmp_str, sizeof(g_settings.libsnes) - 1);
+ free(tmp_str);
+ }
+
+ read_keybinds(conf);
+
+ // TODO: Keybinds.
+
+ config_file_free(conf);
+}
+
+struct bind_map
+{
+ const char *key;
+ const char *btn;
+ const char *axis;
+ int snes_key;
+};
+
+// Big and nasty bind map... :)
+static const struct bind_map bind_maps[2][13] = {
+ {
+ { "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, SNES_FAST_FORWARD_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, SNES_FAST_FORWARD_KEY }
+ }
+};
+
+struct glfw_map
+{
+ const char *str;
+ int key;
+};
+
+// Edit: Not portable to different input systems atm. Might move this map into the driver itself or something.
+static const struct glfw_map glfw_map[] = {
+ { "left", GLFW_KEY_LEFT },
+ { "right", GLFW_KEY_RIGHT },
+ { "up", GLFW_KEY_UP },
+ { "down", GLFW_KEY_DOWN },
+ { "enter", GLFW_KEY_ENTER },
+ { "tab", GLFW_KEY_TAB },
+ { "insert", GLFW_KEY_INSERT },
+ { "del", GLFW_KEY_DEL },
+ { "rshift", GLFW_KEY_RSHIFT },
+ { "shift", GLFW_KEY_LSHIFT },
+ { "ctrl", GLFW_KEY_LCTRL },
+ { "alt", GLFW_KEY_LALT },
+ { "space", GLFW_KEY_SPACE },
+ { "escape", GLFW_KEY_ESC },
+ { "f1", GLFW_KEY_F1 },
+ { "f2", GLFW_KEY_F2 },
+ { "f3", GLFW_KEY_F3 },
+ { "f4", GLFW_KEY_F4 },
+ { "f5", GLFW_KEY_F5 },
+ { "f6", GLFW_KEY_F6 },
+ { "f7", GLFW_KEY_F7 },
+ { "f8", GLFW_KEY_F8 },
+ { "f9", GLFW_KEY_F9 },
+ { "f10", GLFW_KEY_F10 },
+ { "f11", GLFW_KEY_F11 },
+ { "f12", GLFW_KEY_F12 },
+};
+
+static struct snes_keybind *find_snes_bind(unsigned port, int id)
+{
+ struct snes_keybind *binds = g_settings.input.binds[port];
+
+ for (int i = 0; binds[i].id != -1; i++)
+ {
+ if (id == binds[i].id)
+ return &binds[i];
+ }
+ return NULL;
+}
+
+static int find_glfw_bind(const char *str)
+{
+ for (int i = 0; i < sizeof(glfw_map)/sizeof(struct glfw_map); i++)
+ {
+ if (strcasecmp(glfw_map[i].str, str) == 0)
+ return glfw_map[i].key;
+ }
+ return -1;
+}
+
+static int find_glfw_key(const char *str)
+{
+ // If the bind is a normal key-press ...
+ if (strlen(str) == 1 && isalpha(*str))
+ return toupper(*str);
+ else // Check if we have a special mapping for it.
+ return find_glfw_bind(str);
+}
+
+static void read_keybinds(config_file_t *conf)
+{
+ char *tmp_key = NULL;
+ int tmp_btn;
+ char *tmp_axis = NULL;
+
+ for (int j = 0; j < 1; j++)
+ {
+ for (int i = 0; i < sizeof(bind_maps[j])/sizeof(struct bind_map); i++)
+ {
+ struct snes_keybind *bind = find_snes_bind(j, bind_maps[j][i].snes_key);
+ if (!bind)
+ continue;
+
+ if (bind_maps[j][i].key && config_get_string(conf, bind_maps[j][i].key, &tmp_key))
+ {
+ int key = find_glfw_key(tmp_key);
+
+ if (key >= 0)
+ bind->key = key;
+
+ free(tmp_key);
+ tmp_key = NULL;
+ }
+
+ if (bind_maps[j][i].btn && config_get_int(conf, bind_maps[j][i].btn, &tmp_btn))
+ {
+ if (tmp_btn >= 0)
+ bind->joykey = tmp_btn;
+ }
+
+ if (bind_maps[j][i].axis && config_get_string(conf, bind_maps[j][i].axis, &tmp_axis))
+ {
+ if (strlen(tmp_axis) >= 2 && (*tmp_axis == '+' || *tmp_axis == '-'))
+ {
+ int axis = strtol(tmp_axis + 1, NULL, 0);
+ if (*tmp_axis == '+')
+ bind->joyaxis = AXIS_POS(axis);
+ else
+ bind->joyaxis = AXIS_NEG(axis);
+
+ }
+ free(tmp_axis);
+ tmp_axis = NULL;
+ }
+ }
+ }
+
+ char *tmp_str;
+ if (config_get_string(conf, "input_toggle_fullscreen", &tmp_str))
+ {
+ int key = find_glfw_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_glfw_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_glfw_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_glfw_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 7ed812dd08..cc0c518b10 100644
--- a/ssnes.c
+++ b/ssnes.c
@@ -24,15 +24,16 @@
#include
#include
#include
-#include "config.h"
#include "driver.h"
#include "file.h"
-#include "hqflt/pastlib.h"
-#include "hqflt/grayscale.h"
-#include "hqflt/bleed.h"
-#include "hqflt/ntsc.h"
+#include "hqflt/filters.h"
#include "general.h"
+#include "dynamic.h"
+struct global g_extern = {
+ .video_active = true,
+ .audio_active = true,
+};
// 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.
@@ -46,10 +47,10 @@ void set_fast_forward_button(bool new_button_state)
if (new_button_state && !old_button_state)
{
syncing_state = !syncing_state;
- if (video_active)
+ if (g_extern.video_active)
driver.video->set_nonblock_state(driver.video_data, syncing_state);
- if (audio_active)
- driver.audio->set_nonblock_state(driver.audio_data, (audio_sync) ? syncing_state : true);
+ 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;
else
@@ -58,7 +59,7 @@ void set_fast_forward_button(bool new_button_state)
old_button_state = new_button_state;
}
-#if VIDEO_FILTER != FILTER_NONE
+#ifdef HAVE_FILTER
static inline void process_frame (uint16_t * restrict out, const uint16_t * restrict in, unsigned width, unsigned height)
{
int pitch = 1024;
@@ -79,53 +80,54 @@ static inline void process_frame (uint16_t * restrict out, const uint16_t * rest
// Format received is 16-bit 0RRRRRGGGGGBBBBB
static void video_frame(const uint16_t *data, unsigned width, unsigned height)
{
- if ( !video_active )
+ if ( !g_extern.video_active )
return;
-#if VIDEO_FILTER == FILTER_HQ2X
- uint16_t outputHQ2x[width * height * 2 * 2];
-#elif VIDEO_FILTER == FILTER_HQ4X
- uint16_t outputHQ4x[width * height * 4 * 4];
-#elif VIDEO_FILTER == FILTER_NTSC
- uint16_t output_ntsc[SNES_NTSC_OUT_WIDTH(width) * height];
-#endif
-
-#if VIDEO_FILTER != FILTER_NONE
+#ifdef HAVE_FILTER
+ uint16_t output_filter[width * height * 4 * 4];
uint16_t output[width * height];
process_frame(output, data, width, height);
-#endif
-#if VIDEO_FILTER == FILTER_HQ2X
- ProcessHQ2x(output, outputHQ2x);
- if ( !driver.video->frame(driver.video_data, outputHQ2x, width << 1, height << 1, width << 2) )
- video_active = false;
-#elif VIDEO_FILTER == FILTER_HQ4X
- ProcessHQ4x(output, outputHQ4x);
- if ( !driver.video->frame(driver.video_data, outputHQ4x, width << 2, height << 2, width << 3) )
- video_active = false;
-#elif VIDEO_FILTER == FILTER_GRAYSCALE
- grayscale_filter(output, width, height);
- if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) )
- video_active = false;
-#elif VIDEO_FILTER == FILTER_BLEED
- bleed_filter(output, width, height);
- if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) )
- video_active = false;
-#elif VIDEO_FILTER == FILTER_NTSC
- ntsc_filter(output_ntsc, output, width, height);
- if ( !driver.video->frame(driver.video_data, output_ntsc, SNES_NTSC_OUT_WIDTH(width), height, SNES_NTSC_OUT_WIDTH(width) << 1) )
- video_active = false;
+ switch (g_settings.video.filter)
+ {
+ case FILTER_HQ2X:
+ ProcessHQ2x(output, output_filter);
+ if ( !driver.video->frame(driver.video_data, output_filter, width << 1, height << 1, width << 2) )
+ g_extern.video_active = false;
+ break;
+ case FILTER_HQ4X:
+ ProcessHQ4x(output, output_filter);
+ if ( !driver.video->frame(driver.video_data, output_filter, width << 2, height << 2, width << 3) )
+ g_extern.video_active = false;
+ break;
+ case FILTER_GRAYSCALE:
+ grayscale_filter(output, width, height);
+ if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) )
+ g_extern.video_active = false;
+ break;
+ case FILTER_BLEED:
+ bleed_filter(output, width, height);
+ if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) )
+ g_extern.video_active = false;
+ break;
+ case FILTER_NTSC:
+ ntsc_filter(output_filter, output, width, height);
+ if ( !driver.video->frame(driver.video_data, output_filter, SNES_NTSC_OUT_WIDTH(width), height, SNES_NTSC_OUT_WIDTH(width) << 1) )
+ g_extern.video_active = false;
+ break;
+ default:
+ if ( !driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048) )
+ g_extern.video_active = false;
+ }
#else
if ( !driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048) )
- video_active = false;
+ g_extern.video_active = false;
#endif
-
}
-SRC_STATE* source = NULL;
static void audio_sample(uint16_t left, uint16_t right)
{
- if ( !audio_active )
+ if ( !g_extern.audio_active )
return;
static float data[AUDIO_CHUNK_SIZE_NONBLOCKING];
@@ -146,16 +148,16 @@ static void audio_sample(uint16_t left, uint16_t right)
src_data.input_frames = audio_chunk_size / 2;
src_data.output_frames = audio_chunk_size * 8;
src_data.end_of_input = 0;
- src_data.src_ratio = (double)out_rate / (double)in_rate;
+ src_data.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate;
- src_process(source, &src_data);
+ 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 )
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
- audio_active = false;
+ g_extern.audio_active = false;
}
data_ptr = 0;
@@ -169,7 +171,7 @@ static void input_poll(void)
static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id)
{
- const struct snes_keybind *binds[] = { snes_keybinds_1, snes_keybinds_2 };
+ const struct snes_keybind *binds[] = { g_settings.input.binds[0], g_settings.input.binds[1] };
return driver.input->input_state(driver.input_data, binds, port, device, index, id);
}
@@ -193,20 +195,10 @@ static void print_help(void)
puts("Usage: ssnes [rom file] [-h/--help | -s/--save]");
puts("\t-h/--help: Show this help message");
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin");
-#ifdef HAVE_CG
- puts("\t-f/--shader: Path to Cg shader. Will be compiled at runtime.\n");
-#endif
+ puts("\t-c/--config: Path for config file. Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg");
puts("\t-v/--verbose: Verbose logging");
}
-bool fullscreen = START_FULLSCREEN;
-static FILE* rom_file = NULL;
-static char savefile_name_srm[256] = {0};
-bool verbose = false;
-#ifdef HAVE_CG
-char cg_shader_path[256] = DEFAULT_CG_SHADER;
-#endif
-
static void parse_input(int argc, char *argv[])
{
if (argc < 2)
@@ -219,18 +211,12 @@ static void parse_input(int argc, char *argv[])
{ "help", 0, NULL, 'h' },
{ "save", 1, NULL, 's' },
{ "verbose", 0, NULL, 'v' },
-#ifdef HAVE_CG
- { "shader", 1, NULL, 'f' },
-#endif
+ { "config", 0, NULL, 'c' },
{ NULL, 0, NULL, 0 }
};
int option_index = 0;
-#ifdef HAVE_CG
- char optstring[] = "hs:vf:";
-#else
- char optstring[] = "hs:v";
-#endif
+ char optstring[] = "hs:vc:";
for(;;)
{
int c = getopt_long(argc, argv, optstring, opts, &option_index);
@@ -245,18 +231,16 @@ static void parse_input(int argc, char *argv[])
exit(0);
case 's':
- strncpy(savefile_name_srm, optarg, sizeof(savefile_name_srm));
- savefile_name_srm[sizeof(savefile_name_srm)-1] = '\0';
+ 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';
break;
-#ifdef HAVE_CG
- case 'f':
- strncpy(cg_shader_path, optarg, sizeof(cg_shader_path) - 1);
- break;
-#endif
-
case 'v':
- verbose = true;
+ g_extern.verbose = true;
+ break;
+
+ case 'c':
+ strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1);
break;
case '?':
@@ -275,24 +259,20 @@ static void parse_input(int argc, char *argv[])
strcpy(tmp, argv[optind]);
char *dst = strrchr(tmp, '.');
if (dst)
- {
*dst = '\0';
- snes_set_cartridge_basename(tmp);
- }
- else
- snes_set_cartridge_basename(tmp);
+ strncpy(g_extern.basename, tmp, sizeof(g_extern.basename) - 1);
SSNES_LOG("Opening file: \"%s\"\n", argv[optind]);
- rom_file = fopen(argv[optind], "rb");
- if (rom_file == NULL)
+ g_extern.rom_file = fopen(argv[optind], "rb");
+ if (g_extern.rom_file == NULL)
{
SSNES_ERR("Could not open file: \"%s\"\n", optarg);
exit(1);
}
- if (strlen(savefile_name_srm) == 0)
- fill_pathname(savefile_name_srm, argv[optind], ".srm");
+ if (strlen(g_extern.savefile_name_srm) == 0)
+ fill_pathname(g_extern.savefile_name_srm, argv[optind], ".srm");
}
- else if (strlen(savefile_name_srm) == 0)
+ else if (strlen(g_extern.savefile_name_srm) == 0)
{
SSNES_ERR("Need savefile argument when reading rom from stdin.\n");
print_help();
@@ -302,35 +282,41 @@ static void parse_input(int argc, char *argv[])
int main(int argc, char *argv[])
{
- snes_init();
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(rom_file, &rom_buf)) == -1)
+ if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
{
SSNES_ERR("Could not read ROM file.\n");
exit(1);
}
SSNES_LOG("ROM size: %zi bytes\n", rom_len);
- if (rom_file != NULL)
- fclose(rom_file);
+ if (g_extern.rom_file != NULL)
+ fclose(g_extern.rom_file);
- char statefile_name[strlen(savefile_name_srm)+strlen(".state")+1];
- char savefile_name_rtc[strlen(savefile_name_srm)+strlen(".rtc")+1];
+ 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();
- snes_set_video_refresh(video_frame);
- snes_set_audio_sample(audio_sample);
- snes_set_input_poll(input_poll);
- snes_set_input_state(input_state);
+ 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 (!snes_load_cartridge_normal(NULL, rom_buf, rom_len))
+ if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len))
{
SSNES_ERR("ROM file is not valid!\n");
goto error;
@@ -338,7 +324,7 @@ int main(int argc, char *argv[])
free(rom_buf);
- unsigned serial_size = snes_serialize_size();
+ unsigned serial_size = psnes_serialize_size();
uint8_t *serial_data = malloc(serial_size);
if (serial_data == NULL)
{
@@ -346,49 +332,51 @@ int main(int argc, char *argv[])
goto error;
}
- load_save_file(savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
+ load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
load_save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
///// TODO: Modular friendly!!!
for(;;)
{
- bool quitting = glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED);
+ bool quitting = glfwGetKey(g_settings.input.exit_emulator_key) || !glfwGetWindowParam(GLFW_OPENED);
if ( quitting )
break;
- if ( glfwGetKey( SAVE_STATE_KEY ))
+ if ( glfwGetKey( g_settings.input.save_state_key ))
{
write_file(statefile_name, serial_data, serial_size);
}
- else if ( glfwGetKey( LOAD_STATE_KEY ) )
+ else if ( glfwGetKey( g_settings.input.load_state_key ) )
load_state(statefile_name, serial_data, serial_size);
- else if ( glfwGetKey( TOGGLE_FULLSCREEN ) )
+ else if ( glfwGetKey( g_settings.input.toggle_fullscreen_key ) )
{
- fullscreen = !fullscreen;
+ g_settings.video.fullscreen = !g_settings.video.fullscreen;
uninit_drivers();
init_drivers();
}
- snes_run();
+ psnes_run();
}
- save_file(savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
+ save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
- snes_unload_cartridge();
- snes_term();
+ psnes_unload_cartridge();
+ psnes_term();
uninit_drivers();
free(serial_data);
+ uninit_dlsym();
return 0;
error:
- snes_unload_cartridge();
- snes_term();
+ psnes_unload_cartridge();
+ psnes_term();
uninit_drivers();
+ uninit_dlsym();
return 1;
}
diff --git a/ssnes.cfg b/ssnes.cfg
new file mode 100644
index 0000000000..b9d5f9aaba
--- /dev/null
+++ b/ssnes.cfg
@@ -0,0 +1,148 @@
+##### Config file for SSNES
+
+## If enabled, load libsnes from a dynamic location.
+# libsnes_path = "/path/to/libsnes.so"
+
+#### Video
+
+# Windowed xscale and yscale (Real x res: 296 * xscale, real y scale: 224 * xscale)
+# video_xscale = 3.0
+# video_yscale = 3.0
+
+# Fullscreen resolution
+# video_fullscreen_x = 1280
+# video_fullscreen_y = 720
+
+# Start in fullscreen. Can be changed at runtime.
+# video_fullscreen = false
+
+# Video vsync.
+# video_vsync = true
+
+# Smoothens picture with bilinear filtering. Should be disabled if using Cg shaders.
+# video_smooth = true
+
+# Forces rendering area to stay 4:3.
+# video_force_aspect = true
+
+# Path to Cg shader. If enabled
+# video_cg_shader = "/path/to/cg/shader.cg"
+
+# CPU-based filter. Valid ones are: hq2x, hq4x, grayscale, bleed, ntsc.
+# video_filter = ntsc
+
+#### Audio
+
+# Enable audio.
+# audio_enable = true
+
+# Audio output samplerate.
+# audio_out_rate = 48000
+
+# Audio input samplerate from libsnes.
+# Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled.
+# Conversely, increase this slightly if you are experiencing good audio,
+# but lots of dropped frames. Reasonable values for this is 32000 +/- 100 Hz.
+# audio_in_rate = 31950
+
+# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, rsound, roar, openal
+# audio_driver = alsa
+
+# Override the default audio device the audio_driver uses.
+# audio_device =
+
+# Will sync (block) on audio. Recommended.
+# audio_sync = true
+
+# Desired audio latency in milliseconds. Might not be honored if driver can't provide given latency.
+# audio_latency = 64
+
+# libsamplerate quality. Valid values are from 1 to 5. These values map to zero_order_hold, linear, sinc_fastest, sinc_medium and sinc_best.
+# audio_src_quality =
+
+### Input
+
+# Defines axis threshold. Possible values are [0.0, 1.0]
+# input_axis_threshold = 0.6
+
+# Keyboard input. Will recognize normal keypresses and special keys like "left", "right", and so on.
+# input_player1_a = x
+# input_player1_b = z
+# input_player1_y = a
+# input_player1_x = s
+# input_player1_start = enter
+# input_player1_select = rshift
+# input_player1_l = q
+# input_player1_r = w
+# input_player1_left = left
+# input_player1_right = right
+# input_player1_up = up
+# input_player1_down = down
+
+# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output.
+# input_player1_a_btn = 1
+# input_player1_b_btn = 0
+# input_player1_y_btn = 2
+# input_player1_x_btn = 3
+# input_player1_start_btn = 7
+# input_player1_select_btn = 6
+# input_player1_l_btn = 4
+# input_player1_r_btn = 5
+# input_player1_left_btn = 11
+# input_player1_right_btn = 12
+# 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.
+# input_player1_left_axis = -0
+# input_player1_right_axis = +0
+# input_player1_up_axis = +1
+# input_player1_down_axis = -1
+
+# Same stuff, just for player two.
+# input_player2_a =
+# input_player2_b =
+# input_player2_y =
+# input_player2_x =
+# input_player2_start =
+# input_player2_select =
+# input_player2_l =
+# input_player2_r =
+# input_player2_left =
+# input_player2_right =
+# input_player2_up =
+# input_player2_down =
+
+# input_player2_a_btn = 1
+# input_player2_b_btn = 0
+# input_player2_y_btn = 2
+# input_player2_x_btn = 3
+# input_player2_start_btn = 7
+# input_player2_select_btn = 6
+# input_player2_l_btn = 4
+# input_player2_r_btn = 5
+# input_player2_left_btn = 11
+# input_player2_right_btn = 12
+# input_player2_up_btn = 13
+# input_player2_down_btn = 14
+
+
+# input_player2_left_axis = -0
+# input_player2_right_axis = +0
+# input_player2_up_axis = +1
+# input_player2_down_axis = -1
+
+# Toggles fullscreen.
+# input_toggle_fullscreen = f
+# Saves state.
+# input_save_state = f2
+# Loads state.
+# input_load_state = f4
+
+# 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.
+# input_exit_emulator = escape