Merge git://github.com/Themaister/SSNES

This commit is contained in:
Themaister 2011-01-14 23:00:14 +01:00
commit 956d03ba03
25 changed files with 1802 additions and 365 deletions

View File

@ -1,8 +1,10 @@
include config.mk
TARGET = ssnes
TARGET = ssnes tools/ssnes-joyconfig
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o
HEADERS = $(wildcard */*.h) $(wildcard *.h)
LIBS =
DEFINES = -DHAVE_CONFIG_H
@ -74,6 +76,10 @@ else
LIBS += $(libsnes)
endif
ifneq ($(V),1)
Q := @
endif
CFLAGS = -Wall -O3 -g -std=gnu99 -I.
all: $(TARGET) config.mk
@ -83,17 +89,24 @@ config.mk: configure qb/*
@exit 1
ssnes: $(OBJ)
$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
@$(if $(Q), $(shell echo echo LD $@),)
%.o: %.c config.h config.mk
$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
tools/ssnes-joyconfig: $(JOYCONFIG_OBJ)
$(Q)$(CC) -o $@ $(JOYCONFIG_OBJ) $(SDL_LIBS) $(LDFLAGS)
@$(if $(Q), $(shell echo echo LD $@),)
%.o: %.c config.h config.mk $(HEADERS)
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
@$(if $(Q), $(shell echo echo CC $<),)
install: $(TARGET)
install -m755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg
uninstall: $(TARGET)
rm -rf $(DESTDIR)/$(PREFIX)/bin/$(TARGET)
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/{ssnes,ssnes-joyconfig}
rm -f $(DESTDIR)/etc/ssnes.cfg
clean:
rm -f *.o
@ -103,6 +116,8 @@ clean:
rm -f record/*.o
rm -f hqflt/*.o
rm -f hqflt/snes_ntsc/*.o
rm -f input/*.o
rm -f tools/*.o
rm -f $(TARGET)
.PHONY: all install uninstall clean

View File

@ -1,5 +1,6 @@
TARGET = ssnes.exe
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o
CC = gcc
CXX = g++
@ -11,10 +12,10 @@ libsnes ?= -lsnes
LIBS =
DEFINES = -I.
LDFLAGS = -L. -static-libgcc
LDFLAGS = -L. -static-libgcc -s
SRC_LIBS = -lsamplerate-0
SDL_LIBS = -lSDLmain -lSDL
SDL_LIBS = -lSDL
SDL_CFLAGS = -ISDL
ifeq ($(HAVE_SRC), 1)
@ -34,17 +35,27 @@ ifeq ($(HAVE_XML), 1)
LIBS += -lxml2
endif
ifneq ($(V),1)
Q := @
endif
LIBS += $(libsnes)
CFLAGS = -Wall -O3 -g -std=gnu99 -I.
CFLAGS = -Wall -O3 -std=gnu99 -I.
all: $(TARGET)
all: $(TARGET) ssnes-joyconfig.exe
$(TARGET): $(OBJ)
$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
@$(if $(Q), $(shell echo echo LD $@),)
%.o: %.c
$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
@$(if $(Q), $(shell echo echo CC $<),)
ssnes-joyconfig.exe: $(JOBJ)
$(Q)$(CC) -o ssnes-joyconfig.exe $(JOBJ) $(SDL_LIBS) $(LDFLAGS)
@$(if $(Q), $(shell echo echo LD $@),)
clean:
rm -f *.o
@ -53,7 +64,13 @@ clean:
rm -f gfx/*.o
rm -f record/*.o
rm -f hqflt/*.o
rm -f input/*.o
rm -f hqflt/snes_ntsc/*.o
rm -f $(TARGET)
rm -f ssnes-joyconfig.exe
rm -f tools/*.o
.PHONY: all install uninstall clean
dist: all
zip -r ssnes-win32.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll libsamplerate-0.dll ssnes-joyconfig.exe
.PHONY: all install uninstall clean dist

View File

@ -29,8 +29,7 @@
#include <string.h>
#include <assert.h>
#define FRAMES(x) (x / (sizeof(int16_t) * 2))
#define SAMPLES(x) (x / sizeof(int16_t))
#define FRAMES(x) (x / (sizeof(float) * 2))
typedef struct jack
{
@ -61,13 +60,6 @@ static int process_cb(jack_nframes_t nframes, void *data)
if (min_avail > nframes)
min_avail = nframes;
//static int underrun = 0;
//if (min_avail < nframes)
//{
// SSNES_LOG("JACK: Underrun count: %d\n", underrun++);
// fprintf(stderr, "required %d frames, got %d.\n", (int)nframes, (int)min_avail);
//}
for (int i = 0; i < 2; i++)
{
jack_default_audio_sample_t *out = jack_port_get_buffer(jd->ports[i], nframes);
@ -90,25 +82,38 @@ static void shutdown_cb(void *data)
pthread_cond_signal(&jd->cond);
}
static inline void s16_to_float(jack_default_audio_sample_t * restrict out, const int16_t * restrict in, size_t samples)
{
for (int i = 0; i < samples; i++)
out[i] = (float)in[i] / 0x8000;
}
static void parse_ports(const char **dest_ports, const char **jports)
static int parse_ports(char **dest_ports, const char **jports)
{
int parsed = 0;
const char *con = strtok(g_settings.audio.device, ",");
if (con)
dest_ports[parsed++] = con;
dest_ports[parsed++] = strdup(con);
con = strtok(NULL, ",");
if (con)
dest_ports[parsed++] = con;
dest_ports[parsed++] = strdup(con);
for (int i = parsed; i < 2; i++)
dest_ports[i] = jports[i];
dest_ports[i] = strdup(jports[i]);
return 2;
}
static size_t find_buffersize(jack_t *jd, int latency)
{
int frames = latency * g_settings.audio.out_rate / 1000;
int jack_latency = jack_port_get_total_latency(jd->client, jd->ports[0]);
SSNES_LOG("JACK: Jack latency is %d frames.\n", jack_latency);
int buffer_frames = frames - jack_latency;
int min_buffer_frames = jack_get_buffer_size(jd->client) * 2;
SSNES_LOG("JACK: Minimum buffer size is %d frames.\n", min_buffer_frames);
if (buffer_frames < min_buffer_frames)
buffer_frames = min_buffer_frames;
return buffer_frames * sizeof(jack_default_audio_sample_t);
}
static void* __jack_init(const char* device, int rate, int latency)
@ -136,24 +141,8 @@ static void* __jack_init(const char* device, int rate, int latency)
goto error;
}
jack_nframes_t bufsize;
jack_nframes_t jack_bufsize = jack_get_buffer_size(jd->client);
bufsize = (latency * g_settings.audio.out_rate / 1000) > jack_bufsize * 2 ? (latency * g_settings.audio.out_rate / 1000) : jack_bufsize * 2;
bufsize *= sizeof(jack_default_audio_sample_t);
//fprintf(stderr, "jack buffer size: %d\n", (int)bufsize);
for (int i = 0; i < 2; i++)
{
jd->buffer[i] = jack_ringbuffer_create(bufsize);
if (jd->buffer[i] == NULL)
{
SSNES_ERR("Failed to create buffers.\n");
goto error;
}
}
const char *dest_ports[2];
char *dest_ports[2];
jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput);
if (jports == NULL)
{
@ -161,7 +150,7 @@ static void* __jack_init(const char* device, int rate, int latency)
goto error;
}
parse_ports(dest_ports, jports);
int parsed = parse_ports(dest_ports, jports);
if (jack_activate(jd->client) < 0)
{
@ -178,10 +167,25 @@ static void* __jack_init(const char* device, int rate, int latency)
}
}
for (int i = 0; i < parsed; i++)
free(dest_ports[i]);
size_t bufsize = find_buffersize(jd, latency);
SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t)));
for (int i = 0; i < 2; i++)
{
jd->buffer[i] = jack_ringbuffer_create(bufsize);
if (jd->buffer[i] == NULL)
{
SSNES_ERR("Failed to create buffers.\n");
goto error;
}
}
pthread_cond_init(&jd->cond, NULL);
pthread_mutex_init(&jd->cond_lock, NULL);
jack_free(jports);
return jd;
@ -191,19 +195,18 @@ error:
return NULL;
}
static size_t write_buffer(jack_t *jd, const void *buf, size_t size)
static size_t write_buffer(jack_t *jd, const float *buf, size_t size)
{
//fprintf(stderr, "write_buffer: size: %zu\n", size);
// Convert our data to float, deinterleave and write.
jack_default_audio_sample_t out_buffer[size / sizeof(int16_t)];
jack_default_audio_sample_t out_deinterleaved_buffer[2][FRAMES(size)];
s16_to_float(out_buffer, buf, SAMPLES(size));
for (int i = 0; i < 2; i++)
for (size_t j = 0; j < FRAMES(size); j++)
out_deinterleaved_buffer[i][j] = out_buffer[j * 2 + i];
out_deinterleaved_buffer[i][j] = buf[j * 2 + i];
for(;;)
size_t frames = FRAMES(size);
size_t written = 0;
while (written < frames)
{
if (jd->shutdown)
return 0;
@ -212,29 +215,28 @@ static size_t write_buffer(jack_t *jd, const void *buf, size_t size)
avail[0] = jack_ringbuffer_write_space(jd->buffer[0]);
avail[1] = jack_ringbuffer_write_space(jd->buffer[1]);
size_t min_avail = avail[0] < avail[1] ? avail[0] : avail[1];
min_avail /= sizeof(float);
if (jd->nonblock)
size_t write_frames = frames - written > min_avail ? min_avail : frames - written;
if (write_frames > 0)
{
if (min_avail < FRAMES(size) * sizeof(jack_default_audio_sample_t))
size = min_avail * 2 * sizeof(int16_t) / sizeof(jack_default_audio_sample_t);
break;
for (int i = 0; i < 2; i++)
jack_ringbuffer_write(jd->buffer[i], (const char*)&out_deinterleaved_buffer[i][written], write_frames * sizeof(jack_default_audio_sample_t));
written += write_frames;
}
else
{
//fprintf(stderr, "Write avail is: %d\n", (int)min_avail);
if (min_avail >= FRAMES(size) * sizeof(jack_default_audio_sample_t))
break;
pthread_mutex_lock(&jd->cond_lock);
pthread_cond_wait(&jd->cond, &jd->cond_lock);
pthread_mutex_unlock(&jd->cond_lock);
}
pthread_mutex_lock(&jd->cond_lock);
pthread_cond_wait(&jd->cond, &jd->cond_lock);
pthread_mutex_unlock(&jd->cond_lock);
if (jd->nonblock)
break;
}
for (int i = 0; i < 2; i++)
jack_ringbuffer_write(jd->buffer[i], (const char*)out_deinterleaved_buffer[i], FRAMES(size) * sizeof(jack_default_audio_sample_t));
return size;
return written * sizeof(float) * 2;
}
static ssize_t __jack_write(void* data, const void* buf, size_t size)
@ -290,6 +292,7 @@ const audio_driver_t audio_jack = {
.start = __jack_start,
.set_nonblock_state = __jack_set_nonblock_state,
.free = __jack_free,
.float_samples = true,
.ident = "jack"
};

View File

@ -21,7 +21,6 @@
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "general.h"
struct entry_list
{
@ -127,26 +126,23 @@ static bool parse_line(struct entry_list *list, char *line)
return true;
}
static void print_config(config_file_t *conf)
{
struct entry_list *tmp = conf->entries;
while (tmp != NULL)
{
SSNES_LOG("Config => Key: \"%s\", Value: \"%s\"\n", tmp->key, tmp->value);
tmp = tmp->next;
}
}
config_file_t *config_file_new(const char *path)
{
FILE *file = fopen(path, "r");
if (!file)
return NULL;
struct config_file *conf = calloc(1, sizeof(*conf));
if (conf == NULL)
return NULL;
if (path == NULL)
return conf;
FILE *file = fopen(path, "r");
if (!file)
{
free(conf);
return NULL;
}
struct entry_list *tail = conf->entries;
while (!feof(file))
@ -174,9 +170,6 @@ config_file_t *config_file_new(const char *path)
}
fclose(file);
if (g_extern.verbose)
print_config(conf);
return conf;
}
@ -290,4 +283,86 @@ bool config_get_bool(config_file_t *conf, const char *key, bool *in)
return false;
}
void config_set_string(config_file_t *conf, const char *key, const char *val)
{
struct entry_list *list = conf->entries;
struct entry_list *last = list;
while (list != NULL)
{
if (strcmp(key, list->key) == 0)
{
free(list->value);
list->value = strdup(val);
return;
}
last = list;
list = list->next;
}
struct entry_list *elem = calloc(1, sizeof(*elem));
elem->key = strdup(key);
elem->value = strdup(val);
if (last)
last->next = elem;
else
conf->entries = elem;
}
void config_set_double(config_file_t *conf, const char *key, double val)
{
char buf[128];
snprintf(buf, sizeof(buf), "%lf", val);
config_set_string(conf, key, buf);
}
void config_set_int(config_file_t *conf, const char *key, int val)
{
char buf[128];
snprintf(buf, sizeof(buf), "%d", val);
config_set_string(conf, key, buf);
}
void config_set_char(config_file_t *conf, const char *key, char val)
{
char buf[2];
snprintf(buf, sizeof(buf), "%c", val);
config_set_string(conf, key, buf);
}
void config_set_bool(config_file_t *conf, const char *key, bool val)
{
config_set_string(conf, key, val ? "true" : "false");
}
bool config_file_write(config_file_t *conf, const char *path)
{
FILE *file;
if (path)
{
file = fopen(path, "w");
if (!file)
return false;
}
else
file = stdout;
config_file_dump(conf, file);
if (path)
fclose(file);
return true;
}
void config_file_dump(config_file_t *conf, FILE *file)
{
struct entry_list *list = conf->entries;
while (list != NULL)
{
fprintf(file, "%s = \"%s\"\n", list->key, list->value);
list = list->next;
}
}

View File

@ -21,6 +21,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
typedef struct config_file config_file_t;
@ -30,7 +31,7 @@ typedef struct config_file config_file_t;
// - Format is: key = value. There can be as many spaces as you like in-between.
// - Value can be wrapped inside "" for multiword strings. (foo = "hai u")
// Loads a config file. Returns NULL if file doesn't exist.
// Loads a config file. Returns NULL if file doesn't exist. NULL path will create an empty config file.
config_file_t *config_file_new(const char *path);
// Frees config file.
void config_file_free(config_file_t *conf);
@ -48,6 +49,19 @@ bool config_get_string(config_file_t *conf, const char *entry, char **in);
// Extracts a boolean from config. Valid boolean true are "true" and "1". Valid false are "false" and "0". Other values will be treated as an error.
bool config_get_bool(config_file_t *conf, const char *entry, bool *in);
// Setters. Similiar to the getters.
void config_set_double(config_file_t *conf, const char *entry, double value);
void config_set_int(config_file_t *conf, const char *entry, int val);
void config_set_char(config_file_t *conf, const char *entry, char val);
void config_set_string(config_file_t *conf, const char *entry, const char *val);
void config_set_bool(config_file_t *conf, const char *entry, bool val);
// Write the current config to a file.
bool config_file_write(config_file_t *conf, const char *path);
// Dump the current config to an already opened file. Does not close the file.
void config_file_dump(config_file_t *conf, FILE *file);
#endif

View File

@ -139,31 +139,29 @@ static const bool audio_sync = true;
// How far an axis must be tilted to result in a button press
#define AXIS_THRESHOLD 0.5
#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF)
#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U)
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
// To figure out which joypad buttons to use, check jstest or similar.
// Axes are configured using the axis number for the positive (up, right)
// direction and the number's two's-complement (~) for negative directions.
// To use the axis, set the button to -1.
// SDL sometimes reverses the axes for some odd reason, but hey. :D
// Player 1
static const struct snes_keybind snes_keybinds_1[] = {
// SNES button | keyboard key | js btn | js axis |
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) },
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) },
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_POS(1) },
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_NEG(1) },
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE },
{ SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) },
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) },
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_NEG(1) },
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_POS(1) },
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE },
{ SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE },
{ SSNES_SAVE_STATE_KEY, SDLK_F2, NO_BTN, AXIS_NONE },
{ SSNES_LOAD_STATE_KEY, SDLK_F4, NO_BTN, AXIS_NONE },
{ SSNES_FULLSCREEN_TOGGLE_KEY, SDLK_f, NO_BTN, AXIS_NONE },
{ SSNES_QUIT_KEY, SDLK_ESCAPE, NO_BTN, AXIS_NONE },
{ -1 }
};
@ -178,21 +176,66 @@ static const struct snes_keybind snes_keybinds_2[] = {
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_POS(1) },
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_NEG(1) },
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
{ -1 }
};
///// Save state
#define SAVE_STATE_KEY SDLK_F2
///// Load state
#define LOAD_STATE_KEY SDLK_F4
// Player 3
static const struct snes_keybind snes_keybinds_3[] = {
// SNES button | keyboard key | js btn | js axis |
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
{ -1 }
};
//// Toggles between fullscreen and windowed mode.
#define TOGGLE_FULLSCREEN SDLK_f
// Player 4
static const struct snes_keybind snes_keybinds_4[] = {
// SNES button | keyboard key | js btn | js axis |
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
{ -1 }
};
// Player 5
static const struct snes_keybind snes_keybinds_5[] = {
// SNES button | keyboard key | js btn | js axis |
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
{ -1 }
};
#endif

View File

@ -21,6 +21,7 @@
#include <stdio.h>
#include <string.h>
#include "hqflt/filters.h"
#include <assert.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
@ -128,6 +129,9 @@ void uninit_drivers(void)
uninit_audio();
}
#define AUDIO_CHUNK_SIZE_BLOCKING 64
#define AUDIO_CHUNK_SIZE_NONBLOCKING 2048 // So we don't get complete line-noise when fast-forwarding audio.
#define AUDIO_MAX_RATIO 16
void init_audio(void)
{
if (!g_settings.audio.enable)
@ -138,17 +142,35 @@ void init_audio(void)
find_audio_driver();
g_extern.audio_data.block_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING;
g_extern.audio_data.nonblock_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING;
driver.audio_data = driver.audio->init(strlen(g_settings.audio.device) ? g_settings.audio.device : NULL, g_settings.audio.out_rate, g_settings.audio.latency);
if ( driver.audio_data == NULL )
g_extern.audio_active = false;
if (!g_settings.audio.sync && g_extern.audio_active)
{
driver.audio->set_nonblock_state(driver.audio_data, true);
g_extern.audio_data.chunk_size = g_extern.audio_data.nonblock_chunk_size;
}
else
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
int err;
g_extern.source = src_new(g_settings.audio.src_quality, 2, &err);
if (!g_extern.source)
g_extern.audio_active = false;
size_t max_bufsamples = g_extern.audio_data.block_chunk_size > g_extern.audio_data.nonblock_chunk_size ?
g_extern.audio_data.block_chunk_size : g_extern.audio_data.nonblock_chunk_size;
assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO);
assert((g_extern.audio_data.data = malloc(max_bufsamples * sizeof(float))));
g_extern.audio_data.data_ptr = 0;
assert((g_extern.audio_data.outsamples = malloc(max_bufsamples * sizeof(float) * AUDIO_MAX_RATIO)));
assert((g_extern.audio_data.conv_outsamples = malloc(max_bufsamples * sizeof(int16_t) * AUDIO_MAX_RATIO)));
}
void uninit_audio(void)
@ -164,6 +186,10 @@ void uninit_audio(void)
if ( g_extern.source )
src_delete(g_extern.source);
free(g_extern.audio_data.data); g_extern.audio_data.data = NULL;
free(g_extern.audio_data.outsamples); g_extern.audio_data.outsamples = NULL;
free(g_extern.audio_data.conv_outsamples); g_extern.audio_data.conv_outsamples = NULL;
}
void init_video_input(void)

View File

@ -25,8 +25,14 @@
#include <stdint.h>
#include <unistd.h>
#define SSNES_FAST_FORWARD_KEY 0x666 // Hurr, durr
void set_fast_forward_button(bool state);
enum
{
SSNES_FAST_FORWARD_KEY = 0x666, // Hurr, durr
SSNES_LOAD_STATE_KEY,
SSNES_SAVE_STATE_KEY,
SSNES_FULLSCREEN_TOGGLE_KEY,
SSNES_QUIT_KEY,
};
struct snes_keybind
{
@ -55,12 +61,30 @@ typedef struct audio_driver
bool (*start)(void* data);
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about blocking in audio thread? Fast forwarding.
void (*free)(void* data);
bool float_samples; // Defines if driver will take standard floating point samples, or int16_t samples.
const char *ident;
} audio_driver_t;
#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF)
#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U)
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF)
#define AXIS_POS_GET(x) (x & 0xFFFF)
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
#define NO_BTN 0xFFFF // I hope no joypad will ever have this many buttons ... ;)
#define HAT_UP_MASK (1 << 15)
#define HAT_DOWN_MASK (1 << 14)
#define HAT_LEFT_MASK (1 << 13)
#define HAT_RIGHT_MASK (1 << 12)
#define HAT_MAP(x, hat) ((x & ((1 << 12) - 1)) | hat)
#define HAT_MASK (HAT_UP_MASK | HAT_DOWN_MASK | HAT_LEFT_MASK | HAT_RIGHT_MASK)
#define GET_HAT_DIR(x) (x & HAT_MASK)
#define GET_HAT(x) (x & (~HAT_MASK))
typedef struct input_driver
{
void* (*init)(void);

View File

@ -52,6 +52,23 @@ unsigned (*psnes_library_revision_minor)(void);
unsigned (*psnes_library_revision_major)(void);
bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned);
bool (*psnes_load_cartridge_super_game_boy)(
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned);
bool (*psnes_load_cartridge_bsx)(
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned);
bool (*psnes_load_cartridge_bsx_slotted)(
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned);
bool (*psnes_load_cartridge_sufami_turbo)(
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned);
void (*psnes_set_controller_port_device)(bool, unsigned);
bool (*psnes_get_region)(void);
unsigned (*psnes_serialize_size)(void);
bool (*psnes_serialize)(uint8_t*, unsigned);
@ -84,7 +101,13 @@ static void load_dynamic(void)
SYM(snes_library_revision_minor);
SYM(snes_library_revision_major);
SYM(snes_run);
SYM(snes_get_region);
SYM(snes_load_cartridge_normal);
SYM(snes_load_cartridge_super_game_boy);
SYM(snes_load_cartridge_bsx);
SYM(snes_load_cartridge_bsx_slotted);
SYM(snes_load_cartridge_sufami_turbo);
SYM(snes_set_controller_port_device);
SYM(snes_serialize_size);
SYM(snes_serialize);
SYM(snes_unserialize);
@ -111,7 +134,13 @@ static void set_statics(void)
SSYM(snes_library_revision_minor);
SSYM(snes_library_revision_major);
SSYM(snes_run);
SSYM(snes_get_region);
SSYM(snes_load_cartridge_normal);
SSYM(snes_load_cartridge_super_game_boy);
SSYM(snes_load_cartridge_bsx);
SSYM(snes_load_cartridge_bsx_slotted);
SSYM(snes_load_cartridge_sufami_turbo);
SSYM(snes_set_controller_port_device);
SSYM(snes_serialize_size);
SSYM(snes_serialize);
SSYM(snes_unserialize);

View File

@ -35,6 +35,24 @@ extern unsigned (*psnes_library_revision_minor)(void);
extern unsigned (*psnes_library_revision_major)(void);
extern bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned);
extern bool (*psnes_load_cartridge_super_game_boy)(
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned);
extern bool (*psnes_load_cartridge_bsx)(
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned);
extern bool (*psnes_load_cartridge_bsx_slotted)(
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned);
extern bool (*psnes_load_cartridge_sufami_turbo)(
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned,
const char*, const uint8_t*, unsigned);
extern void (*psnes_set_controller_port_device)(bool, unsigned);
extern bool (*psnes_get_region)(void);
extern unsigned (*psnes_serialize_size)(void);
extern bool (*psnes_serialize)(uint8_t*, unsigned);

258
file.c
View File

@ -165,3 +165,261 @@ void save_file(const char* path, int type)
if ( data && size > 0 )
write_file(path, data, size);
}
static bool load_sgb_rom(void)
{
void *rom_buf = NULL;
ssize_t rom_len = 0;
FILE *extra_rom = NULL;
void *extra_rom_buf = NULL;
ssize_t extra_rom_len = 0;
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
{
SSNES_ERR("Could not read ROM file.\n");
goto error;
}
extra_rom = fopen(g_extern.gb_rom_path, "rb");
if (!extra_rom)
{
SSNES_ERR("Couldn't open GameBoy ROM!\n");
goto error;
}
if ((extra_rom_len = read_file(extra_rom, &extra_rom_buf)) == -1)
{
SSNES_ERR("Cannot read GameBoy rom.\n");
goto error;
}
if (!psnes_load_cartridge_super_game_boy(
NULL, rom_buf, rom_len,
NULL, extra_rom_buf, extra_rom_len))
{
SSNES_ERR("Cannot load SGB/GameBoy rom.\n");
goto error;
}
if (g_extern.rom_file)
fclose(g_extern.rom_file);
if (extra_rom)
fclose(extra_rom);
free(rom_buf);
free(extra_rom_buf);
return true;
error:
if (g_extern.rom_file)
fclose(g_extern.rom_file);
if (extra_rom)
fclose(extra_rom);
free(rom_buf);
free(extra_rom_buf);
return false;
}
static bool load_bsx_rom(bool slotted)
{
void *rom_buf = NULL;
ssize_t rom_len = 0;
FILE *extra_rom = NULL;
void *extra_rom_buf = NULL;
ssize_t extra_rom_len = 0;
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
{
SSNES_ERR("Could not read ROM file.\n");
goto error;
}
extra_rom = fopen(g_extern.bsx_rom_path, "rb");
if (!extra_rom)
{
SSNES_ERR("Couldn't open BSX game rom!\n");
goto error;
}
if ((extra_rom_len = read_file(extra_rom, &extra_rom_buf)) == -1)
{
SSNES_ERR("Cannot read BSX game rom.\n");
goto error;
}
if (slotted)
{
if (!psnes_load_cartridge_bsx_slotted(
NULL, rom_buf, rom_len,
NULL, extra_rom_buf, extra_rom_len))
{
SSNES_ERR("Cannot load BSX slotted rom.\n");
goto error;
}
}
else
{
if (!psnes_load_cartridge_bsx(
NULL, rom_buf, rom_len,
NULL, extra_rom_buf, extra_rom_len))
{
SSNES_ERR("Cannot load BSX rom.\n");
goto error;
}
}
if (g_extern.rom_file)
fclose(g_extern.rom_file);
if (extra_rom)
fclose(extra_rom);
free(rom_buf);
free(extra_rom_buf);
return true;
error:
if (g_extern.rom_file)
fclose(g_extern.rom_file);
if (extra_rom)
fclose(extra_rom);
free(rom_buf);
free(extra_rom_buf);
return false;
}
static bool load_sufami_rom(void)
{
void *rom_buf = NULL;
ssize_t rom_len = 0;
FILE *extra_rom[2] = {NULL};
void *extra_rom_buf[2] = {NULL};
ssize_t extra_rom_len[2] = {0};
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
{
SSNES_ERR("Could not read ROM file.\n");
goto error;
}
const char *roms[2] = { g_extern.sufami_rom_path[0], g_extern.sufami_rom_path[1] };
for (int i = 0; i < 2; i++)
{
if (strlen(roms[i]) > 0)
{
extra_rom[i] = fopen(roms[i], "rb");
if (!extra_rom[i])
{
SSNES_ERR("Couldn't open BSX game rom!\n");
goto error;
}
if ((extra_rom_len[i] = read_file(extra_rom[i], &extra_rom_buf[i])) == -1)
{
SSNES_ERR("Cannot read BSX game rom.\n");
goto error;
}
}
}
if (!psnes_load_cartridge_sufami_turbo(
NULL, rom_buf, rom_len,
NULL, extra_rom_buf[0], extra_rom_len[0],
NULL, extra_rom_buf[1], extra_rom_len[1]))
{
SSNES_ERR("Cannot load Sufami Turbo rom.\n");
goto error;
}
if (g_extern.rom_file)
fclose(g_extern.rom_file);
for (int i = 0; i < 2; i++)
{
if (extra_rom[i])
fclose(extra_rom[i]);
free(extra_rom_buf[i]);
}
free(rom_buf);
return true;
error:
if (g_extern.rom_file)
fclose(g_extern.rom_file);
for (int i = 0; i < 2; i++)
{
if (extra_rom[i])
fclose(extra_rom[i]);
free(extra_rom_buf[i]);
}
free(rom_buf);
return false;
}
static bool load_normal_rom(void)
{
void *rom_buf = NULL;
ssize_t rom_len = 0;
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
{
SSNES_ERR("Could not read ROM file.\n");
return false;
}
if (g_extern.rom_file != NULL)
fclose(g_extern.rom_file);
SSNES_LOG("ROM size: %d bytes\n", (int)rom_len);
if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len))
{
SSNES_ERR("ROM file is not valid!\n");
free(rom_buf);
return false;
}
free(rom_buf);
return true;
}
bool init_rom_file(enum ssnes_game_type type)
{
switch (type)
{
case SSNES_CART_SGB:
if (!load_sgb_rom())
return false;
break;
case SSNES_CART_NORMAL:
if (!load_normal_rom())
return false;
break;
case SSNES_CART_BSX:
if (!load_bsx_rom(false))
return false;
break;
case SSNES_CART_BSX_SLOTTED:
if (!load_bsx_rom(true))
return false;
break;
case SSNES_CART_SUFAMI:
if (!load_sufami_rom())
return false;
break;
default:
SSNES_ERR("Invalid ROM type!\n");
return false;
}
return true;
}

4
file.h
View File

@ -19,10 +19,12 @@
#ifndef __SSNES_FILE_H
#define __SSNES_FILE_H
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/types.h>
#include "general.h"
ssize_t read_file(FILE *file, void **buf);
@ -31,4 +33,6 @@ void write_file(const char* path, uint8_t* data, size_t size);
void load_save_file(const char* path, int type);
void save_file(const char* path, int type);
bool init_rom_file(enum ssnes_game_type type);
#endif

View File

@ -33,8 +33,9 @@
#endif
#define MAX_PLAYERS 2
#define MAX_BINDS 14
#define MAX_PLAYERS 5
#define MAX_BINDS 18 // Needs to be increased every time there are new binds added.
#define SSNES_NO_JOYPAD 0xFFFF
struct settings
{
struct
@ -70,16 +71,22 @@ struct settings
{
char driver[32];
struct snes_keybind binds[MAX_PLAYERS][MAX_BINDS];
int save_state_key;
int load_state_key;
int toggle_fullscreen_key;
int exit_emulator_key;
float axis_threshold;
unsigned joypad_map[MAX_PLAYERS];
} input;
char libsnes[256];
};
enum ssnes_game_type
{
SSNES_CART_NORMAL = 0,
SSNES_CART_SGB,
SSNES_CART_BSX,
SSNES_CART_BSX_SLOTTED,
SSNES_CART_SUFAMI,
};
struct global
{
bool verbose;
@ -87,10 +94,40 @@ struct global
bool audio_active;
bool video_active;
bool has_mouse[2];
bool has_scope[2];
bool has_justifier;
bool has_justifiers;
bool has_multitap;
FILE *rom_file;
char savefile_name_srm[256];
enum ssnes_game_type game_type;
char gb_rom_path[256];
char bsx_rom_path[256];
char sufami_rom_path[2][256];
char config_path[256];
char basename[256];
char savefile_name_srm[256];
char savefile_name_rtc[512]; // Make sure that fill_pathname has space.
char savefile_name_psrm[512];
char savefile_name_asrm[512];
char savefile_name_bsrm[512];
char savestate_name[256];
struct
{
float *data;
size_t data_ptr;
size_t chunk_size;
size_t nonblock_chunk_size;
size_t block_chunk_size;
float *outsamples;
int16_t *conv_outsamples;
} audio_data;
#ifdef HAVE_FFMPEG
ffemu_t *rec;

View File

@ -49,6 +49,8 @@
#include "shader_glsl.h"
#endif
#include "gl_common.h"
static const GLfloat vertexes[] = {
0, 0, 0,
0, 1, 0,
@ -64,7 +66,6 @@ static const GLfloat tex_coords[] = {
};
static bool keep_aspect = true;
static GLuint gl_width = 0, gl_height = 0;
typedef struct gl
{
bool vsync;
@ -218,19 +219,19 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i
if (gl->should_resize)
{
gl->should_resize = false;
SDL_SetVideoMode(gl->win_width, gl->win_height, 32, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0));
SDL_SetVideoMode(gl->win_width, gl->win_height, 0, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0));
set_viewport(gl);
}
glClear(GL_COLOR_BUFFER_BIT);
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl_width, gl_height);
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl->vp_width, gl->vp_height);
if (width != gl->last_width || height != gl->last_height) // res change. need to clear out texture.
{
gl->last_width = width;
gl->last_height = height;
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->tex_w);
uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint16_t));
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, gl->tex_w, gl->tex_h, GL_BGRA,
@ -302,10 +303,11 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
if (!SDL_SetVideoMode(video->width, video->height, 32, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
if (!SDL_SetVideoMode(video->width, video->height, 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
return NULL;
int attr = 0;
SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr);
if (attr <= 0 && video->vsync)
@ -315,8 +317,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
if (attr <= 0)
SSNES_WARN("GL double buffer has not been enabled!\n");
// Remove that ugly mouse :D
SDL_ShowCursor(SDL_DISABLE);
gl_t *gl = calloc(1, sizeof(gl_t));
if (!gl)
@ -325,6 +325,18 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
gl->win_width = video->width;
gl->win_height = video->height;
gl->vsync = video->vsync;
set_viewport(gl);
if (!gl_shader_init())
{
SSNES_ERR("Shader init failed.\n");
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(gl);
return NULL;
}
// Remove that ugly mouse :D
SDL_ShowCursor(SDL_DISABLE);
keep_aspect = video->force_aspect;
if ( video->smooth )
@ -332,8 +344,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
else
gl->tex_filter = GL_NEAREST;
set_viewport(gl);
glEnable(GL_TEXTURE_2D);
glDisable(GL_DITHER);
glDisable(GL_DEPTH_TEST);
@ -347,6 +357,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
glGenTextures(1, &gl->texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
@ -371,8 +382,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
gl->last_width = gl->tex_w;
gl->last_height = gl->tex_h;
gl_shader_init();
// Hook up SDL input driver to get SDL_QUIT events and RESIZE.
sdl_input_t *sdl_input = input_sdl.init();
if (sdl_input)
@ -386,6 +395,13 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
}
else
*input = NULL;
if (!gl_check_error())
{
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(gl);
return NULL;
}
return gl;
}

59
gfx/gl_common.h Normal file
View File

@ -0,0 +1,59 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GL_COMMON_H
#define __GL_COMMON_H
#include "general.h"
static inline bool gl_check_error(void)
{
int error = glGetError();
switch (error)
{
case GL_INVALID_ENUM:
SSNES_ERR("GL: Invalid enum.\n");
break;
case GL_INVALID_VALUE:
SSNES_ERR("GL: Invalid value.\n");
break;
case GL_INVALID_OPERATION:
SSNES_ERR("GL: Invalid operation.\n");
break;
case GL_STACK_OVERFLOW:
SSNES_ERR("GL: Stack overflow. (wtf)\n");
break;
case GL_STACK_UNDERFLOW:
SSNES_ERR("GL: Stack underflow. (:v)\n");
break;
case GL_OUT_OF_MEMORY:
SSNES_ERR("GL: Out of memory. Harhar.\n");
break;
case GL_TABLE_TOO_LARGE:
SSNES_ERR("GL: Table too large. Big tables scare you! :(\n");
break;
case GL_NO_ERROR:
return true;
break;
default:
SSNES_ERR("Non specified error :v\n");
}
return false;
}
#endif

View File

@ -34,6 +34,8 @@
#define GL_GLEXT_PROTOTYPES
#include <GL/glext.h>
#include "gl_common.h"
static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL;
static PFNGLUSEPROGRAMPROC pglUseProgram = NULL;
static PFNGLCREATESHADERPROC pglCreateShader = NULL;
@ -49,6 +51,8 @@ static PFNGLUNIFORM2FVPROC pglUniform2fv = NULL;
static PFNGLUNIFORM4FVPROC pglUniform4fv = NULL;
static PFNGLGETSHADERIVPROC pglGetShaderiv = NULL;
static PFNGLGETSHADERINFOLOGPROC pglGetShaderInfoLog = NULL;
static PFNGLGETPROGRAMIVPROC pglGetProgramiv = NULL;
static PFNGLGETPROGRAMINFOLOGPROC pglGetProgramInfoLog = NULL;
static bool glsl_enable = false;
static GLuint gl_program;
@ -154,6 +158,20 @@ static void print_shader_log(GLuint obj)
SSNES_LOG("Shader log: %s\n", info_log);
}
static void print_linker_log(GLuint obj)
{
int info_len = 0;
int max_len;
pglGetProgramiv(obj, GL_INFO_LOG_LENGTH, &max_len);
char info_log[max_len];
pglGetProgramInfoLog(obj, max_len, &info_len, info_log);
if (info_len > 0)
SSNES_LOG("Linker log: %s\n", info_log);
}
bool gl_glsl_init(const char *path)
{
// Load shader functions.
@ -172,13 +190,15 @@ bool gl_glsl_init(const char *path)
pglUniform4fv = SDL_GL_GetProcAddress("glUniform4fv");
pglGetShaderiv = SDL_GL_GetProcAddress("glGetShaderiv");
pglGetShaderInfoLog = SDL_GL_GetProcAddress("glGetShaderInfoLog");
pglGetProgramiv = SDL_GL_GetProcAddress("glGetProgramiv");
pglGetProgramInfoLog = SDL_GL_GetProcAddress("glGetProgramInfoLog");
SSNES_LOG("Checking GLSL shader support ...\n");
bool shader_support = pglCreateProgram && pglUseProgram && pglCreateShader
&& pglDeleteShader && pglShaderSource && pglCompileShader && pglAttachShader
&& pglDetachShader && pglLinkProgram && pglGetUniformLocation
&& pglUniform1i && pglUniform2fv && pglUniform4fv
&& pglGetShaderiv && pglGetShaderInfoLog;
&& pglGetShaderiv && pglGetShaderInfoLog && pglGetProgramiv && pglGetProgramInfoLog;
if (!shader_support)
{
@ -218,8 +238,15 @@ bool gl_glsl_init(const char *path)
{
pglLinkProgram(gl_program);
pglUseProgram(gl_program);
print_linker_log(gl_program);
GLint location = pglGetUniformLocation(gl_program, "rubyTexture");
pglUniform1i(location, 0);
}
if (!gl_check_error())
return false;
glsl_enable = true;
return true;
}
@ -246,6 +273,7 @@ void gl_glsl_set_params(unsigned width, unsigned height,
float textureSize[2] = {tex_width, tex_height};
location = pglGetUniformLocation(gl_program, "rubyTextureSize");
pglUniform2fv(location, 1, textureSize);
}
}

View File

@ -91,7 +91,8 @@ output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXU
else
mcol.rb = 0.7;
OUT.color = pow(mcol*(col * weights + col2 * weights2), 1.0/2.2);
//OUT.color = pow(mcol*(col * weights + col2 * weights2), 1.0/2.2);
OUT.color = 1.0;
return OUT;
}

View File

@ -30,6 +30,7 @@
#include "bleed.h"
#include "ntsc.h"
#define FILTER_NONE 0
#define FILTER_HQ2X 1
#define FILTER_HQ4X 2
#define FILTER_GRAYSCALE 3

View File

@ -34,29 +34,43 @@ static void* sdl_input_init(void)
if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
return NULL;
SDL_JoystickEventState(SDL_IGNORE);
sdl->num_joysticks = SDL_NumJoysticks();
if (sdl->num_joysticks > 2)
sdl->num_joysticks = 2;
for (unsigned i = 0; i < sdl->num_joysticks; i++)
{
sdl->joysticks[i] = SDL_JoystickOpen(i);
if (!sdl->joysticks[i])
{
SSNES_ERR("Couldn't open SDL joystick %d\n", i);
free(sdl);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
return NULL;
}
SSNES_LOG("Opened Joystick: %s\n", SDL_JoystickName(i));
sdl->num_axes[i] = SDL_JoystickNumAxes(sdl->joysticks[i]);
sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]);
for (unsigned i = 0; i < MAX_PLAYERS; i++)
{
if (g_settings.input.joypad_map[i] == SSNES_NO_JOYPAD)
continue;
if (sdl->num_joysticks > g_settings.input.joypad_map[i])
{
sdl->joysticks[i] = SDL_JoystickOpen(g_settings.input.joypad_map[i]);
if (!sdl->joysticks[i])
{
SSNES_ERR("Couldn't open SDL joystick #%u on SNES port %u\n", g_settings.input.joypad_map[i], i + 1);
free(sdl);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
return NULL;
}
SSNES_LOG("Opened Joystick: %s #%u on port %u\n",
SDL_JoystickName(g_settings.input.joypad_map[i]), g_settings.input.joypad_map[i], i + 1);
sdl->num_axes[i] = SDL_JoystickNumAxes(sdl->joysticks[i]);
sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]);
sdl->num_hats[i] = SDL_JoystickNumHats(sdl->joysticks[i]);
}
else
{
SSNES_WARN("Desired SDL joystick #%u on port %u, but SDL can only detect %u joysticks ...\n",
g_settings.input.joypad_map[i], i + 1, sdl->num_joysticks);
}
}
return sdl;
}
static bool sdl_key_pressed(void *data, int key)
static bool sdl_key_pressed(int key)
{
int num_keys;
Uint8 *keymap = SDL_GetKeyState(&num_keys);
@ -67,27 +81,60 @@ static bool sdl_key_pressed(void *data, int key)
return keymap[key];
}
static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key)
static bool sdl_joykey_pressed(sdl_input_t *sdl, int port_num, uint16_t joykey)
{
if (sdl_key_pressed(sdl, key->key))
return true;
if (port_num >= sdl->num_joysticks)
return false;
if (key->joykey < sdl->num_buttons[port_num] && SDL_JoystickGetButton(sdl->joysticks[port_num], key->joykey))
return true;
if (key->joyaxis != AXIS_NONE)
// Check hat.
if (GET_HAT_DIR(joykey))
{
if (AXIS_NEG_GET(key->joyaxis) < sdl->num_axes[port_num])
int hat = GET_HAT(joykey);
if (hat < sdl->num_hats[port_num])
{
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(key->joyaxis));
Uint8 dir = SDL_JoystickGetHat(sdl->joysticks[port_num], hat);
switch (GET_HAT_DIR(joykey))
{
case HAT_UP_MASK:
if (dir & SDL_HAT_UP)
return true;
break;
case HAT_DOWN_MASK:
if (dir & SDL_HAT_DOWN)
return true;
break;
case HAT_LEFT_MASK:
if (dir & SDL_HAT_LEFT)
return true;
break;
case HAT_RIGHT_MASK:
if (dir & SDL_HAT_RIGHT)
return true;
break;
default:
break;
}
}
}
else // Check the button
{
if (joykey < sdl->num_buttons[port_num] && SDL_JoystickGetButton(sdl->joysticks[port_num], joykey))
return true;
}
return false;
}
static bool sdl_axis_pressed(sdl_input_t *sdl, int port_num, uint32_t joyaxis)
{
if (joyaxis != AXIS_NONE)
{
if (AXIS_NEG_GET(joyaxis) < sdl->num_axes[port_num])
{
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(joyaxis));
float scaled = (float)val / 0x8000;
if (scaled < -g_settings.input.axis_threshold)
return true;
}
if (AXIS_POS_GET(key->joyaxis) < sdl->num_axes[port_num])
if (AXIS_POS_GET(joyaxis) < sdl->num_axes[port_num])
{
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(key->joyaxis));
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(joyaxis));
float scaled = (float)val / 0x8000;
if (scaled > g_settings.input.axis_threshold)
return true;
@ -97,45 +144,166 @@ static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_key
return false;
}
static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id)
static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key)
{
sdl_input_t *sdl = data;
if (device != SNES_DEVICE_JOYPAD)
return 0;
if (sdl_key_pressed(key->key))
return true;
if (sdl->joysticks[port_num] == NULL)
return false;
if (sdl_joykey_pressed(sdl, port_num, key->joykey))
return true;
if (sdl_axis_pressed(sdl, port_num, key->joyaxis))
return true;
const struct snes_keybind *snes_keybinds = binds[port == SNES_PORT_1 ? 0 : 1];
return false;
}
static bool sdl_bind_button_pressed(void *data, int key)
{
// Only let player 1 use special binds called from main loop.
const struct snes_keybind *binds = g_settings.input.binds[0];
for (int i = 0; binds[i].id != -1; i++)
{
if (binds[i].id == key)
return sdl_is_pressed(data, 0, &binds[i]);
}
return false;
}
static int16_t sdl_joypad_device_state(sdl_input_t *sdl, const struct snes_keybind **binds,
int port_num, unsigned device, unsigned index, unsigned id)
{
const struct snes_keybind *snes_keybinds = binds[port_num];
// Checks if button is pressed, and sets fast-forwarding state
bool pressed = false;
int port_num = port == SNES_PORT_1 ? 0 : 1;
for (int i = 0; snes_keybinds[i].id != -1; i++)
{
if (snes_keybinds[i].id == SSNES_FAST_FORWARD_KEY)
set_fast_forward_button(sdl_is_pressed(sdl, port_num, &snes_keybinds[i]));
else if (!pressed && snes_keybinds[i].id == (int)id)
pressed = sdl_is_pressed(sdl, port_num, &snes_keybinds[i]);
if (snes_keybinds[i].id == (int)id)
return sdl_is_pressed(sdl, port_num, &snes_keybinds[i]);
}
return pressed;
return false;
}
static int16_t sdl_mouse_device_state(sdl_input_t *sdl, bool port, unsigned id)
{
// Might implement support for joypad mapping later.
(void)port;
switch (id)
{
case SNES_DEVICE_ID_MOUSE_LEFT:
return sdl->mouse_l;
case SNES_DEVICE_ID_MOUSE_RIGHT:
return sdl->mouse_r;
case SNES_DEVICE_ID_MOUSE_X:
return sdl->mouse_x;
case SNES_DEVICE_ID_MOUSE_Y:
return sdl->mouse_y;
default:
return 0;
}
}
// TODO: Missing some controllers, but hey :)
static int16_t sdl_scope_device_state(sdl_input_t *sdl, unsigned id)
{
switch (id)
{
case SNES_DEVICE_ID_SUPER_SCOPE_X:
return sdl->mouse_x;
case SNES_DEVICE_ID_SUPER_SCOPE_Y:
return sdl->mouse_y;
case SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER:
return sdl->mouse_l;
case SNES_DEVICE_ID_SUPER_SCOPE_CURSOR:
return sdl->mouse_m;
case SNES_DEVICE_ID_SUPER_SCOPE_TURBO:
return sdl->mouse_r;
default:
return 0;
}
}
// TODO: Support two players.
static int16_t sdl_justifier_device_state(sdl_input_t *sdl, unsigned index, unsigned id)
{
if (index == 0)
{
switch (id)
{
case SNES_DEVICE_ID_JUSTIFIER_X:
return sdl->mouse_x;
case SNES_DEVICE_ID_JUSTIFIER_Y:
return sdl->mouse_y;
case SNES_DEVICE_ID_JUSTIFIER_TRIGGER:
return sdl->mouse_l;
case SNES_DEVICE_ID_JUSTIFIER_START:
return sdl->mouse_r;
default:
return 0;
}
}
else
return 0;
}
static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id)
{
switch (device)
{
case SNES_DEVICE_JOYPAD:
return sdl_joypad_device_state(data, binds, port == SNES_PORT_1 ? 0 : 1, device, index, id);
case SNES_DEVICE_MULTITAP:
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_2) ? 1 + index : 0, device, index, id);
case SNES_DEVICE_MOUSE:
return sdl_mouse_device_state(data, port, id);
case SNES_DEVICE_SUPER_SCOPE:
return sdl_scope_device_state(data, id);
case SNES_DEVICE_JUSTIFIER:
case SNES_DEVICE_JUSTIFIERS:
return sdl_justifier_device_state(data, index, id);
default:
return 0;
}
}
static void sdl_input_free(void *data)
{
if (data)
{
// Flush out all pending events.
SDL_Event event;
while (SDL_PollEvent(&event));
sdl_input_t *sdl = data;
for (int i = 0; i < sdl->num_joysticks; i++)
SDL_JoystickClose(sdl->joysticks[i]);
for (int i = 0; i < MAX_PLAYERS; i++)
{
if (sdl->joysticks[i])
SDL_JoystickClose(sdl->joysticks[i]);
}
free(data);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
}
static void sdl_poll_mouse(sdl_input_t *sdl)
{
int _x, _y;
Uint8 btn = SDL_GetRelativeMouseState(&_x, &_y);
sdl->mouse_x = _x;
sdl->mouse_y = _y;
sdl->mouse_l = SDL_BUTTON(SDL_BUTTON_LEFT) & btn ? 1 : 0;
sdl->mouse_r = SDL_BUTTON(SDL_BUTTON_RIGHT) & btn ? 1 : 0;
sdl->mouse_m = SDL_BUTTON(SDL_BUTTON_MIDDLE) & btn ? 1 : 0;
}
static void sdl_input_poll(void *data)
{
SDL_PumpEvents();
SDL_Event event;
SDL_JoystickUpdate();
sdl_poll_mouse(data);
sdl_input_t *sdl = data;
// Search for events...
@ -170,7 +338,7 @@ const input_driver_t input_sdl = {
.init = sdl_input_init,
.poll = sdl_input_poll,
.input_state = sdl_input_state,
.key_pressed = sdl_key_pressed,
.key_pressed = sdl_bind_button_pressed,
.free = sdl_input_free,
.ident = "sdl"
};

View File

@ -19,11 +19,13 @@
#define __SSNES_SDL_INPUT_H
#include "SDL.h"
#include "general.h"
typedef struct sdl_input
{
SDL_Joystick *joysticks[2];
unsigned num_axes[2];
unsigned num_buttons[2];
SDL_Joystick *joysticks[MAX_PLAYERS];
unsigned num_axes[MAX_PLAYERS];
unsigned num_buttons[MAX_PLAYERS];
unsigned num_hats[MAX_PLAYERS];
unsigned num_joysticks;
// A video driver could pre-init with the SDL driver and have it handle resizing events...
@ -31,6 +33,8 @@ typedef struct sdl_input
bool *should_resize;
unsigned *new_width;
unsigned *new_height;
int16_t mouse_x, mouse_y;
int16_t mouse_l, mouse_r, mouse_m;
} sdl_input_t;
#endif

View File

@ -17,10 +17,10 @@
#include "general.h"
#include "conf/config_file.h"
#include "config.def.h"
#include <assert.h>
#include <string.h>
#include "hqflt/filters.h"
#include "config.def.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
@ -96,6 +96,7 @@ static void set_defaults(void)
g_settings.video.smooth = video_smooth;
g_settings.video.force_aspect = force_aspect;
g_settings.video.aspect_ratio = SNES_ASPECT_RATIO;
g_settings.video.filter = FILTER_NONE;
g_settings.audio.enable = audio_enable;
g_settings.audio.out_rate = out_rate;
@ -108,14 +109,20 @@ static void set_defaults(void)
assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1));
assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2));
assert(sizeof(g_settings.input.binds[2]) >= sizeof(snes_keybinds_3));
assert(sizeof(g_settings.input.binds[3]) >= sizeof(snes_keybinds_4));
assert(sizeof(g_settings.input.binds[4]) >= sizeof(snes_keybinds_5));
memcpy(g_settings.input.binds[0], snes_keybinds_1, sizeof(snes_keybinds_1));
memcpy(g_settings.input.binds[1], snes_keybinds_2, sizeof(snes_keybinds_2));
memcpy(g_settings.input.binds[2], snes_keybinds_3, sizeof(snes_keybinds_3));
memcpy(g_settings.input.binds[3], snes_keybinds_4, sizeof(snes_keybinds_4));
memcpy(g_settings.input.binds[4], snes_keybinds_5, sizeof(snes_keybinds_5));
g_settings.input.save_state_key = SAVE_STATE_KEY;
g_settings.input.load_state_key = LOAD_STATE_KEY;
g_settings.input.toggle_fullscreen_key = TOGGLE_FULLSCREEN;
g_settings.input.axis_threshold = AXIS_THRESHOLD;
g_settings.input.exit_emulator_key = SDLK_ESCAPE;
for (int i = 0; i < 2; i++)
g_settings.input.joypad_map[i] = i;
for (int i = 2; i < MAX_PLAYERS; i++)
g_settings.input.joypad_map[i] = SSNES_NO_JOYPAD;
}
void parse_config(void)
@ -164,6 +171,9 @@ void parse_config(void)
if (conf == NULL)
return;
if (g_extern.verbose)
config_file_dump(conf, stderr);
int tmp_int;
double tmp_double;
bool tmp_bool;
@ -246,6 +256,22 @@ void parse_config(void)
if (config_get_double(conf, "input_axis_threshold", &tmp_double))
g_settings.input.axis_threshold = tmp_double;
// Joypad mapping.
if (config_get_int(conf, "input_player1_joypad_index", &tmp_int))
g_settings.input.joypad_map[0] = tmp_int;
if (config_get_int(conf, "input_player2_joypad_index", &tmp_int))
g_settings.input.joypad_map[1] = tmp_int;
if (config_get_int(conf, "input_player3_joypad_index", &tmp_int))
g_settings.input.joypad_map[2] = tmp_int;
if (config_get_int(conf, "input_player4_joypad_index", &tmp_int))
g_settings.input.joypad_map[3] = tmp_int;
if (config_get_int(conf, "input_player5_joypad_index", &tmp_int))
g_settings.input.joypad_map[4] = tmp_int;
// Audio settings.
if (config_get_bool(conf, "audio_enable", &tmp_bool))
g_settings.audio.enable = tmp_bool;
@ -311,38 +337,105 @@ struct bind_map
int snes_key;
};
#define DECLARE_BIND(x, bind) { "input_" #x, "input_" #x "_btn", "input_" #x "_axis", bind },
// Big and nasty bind map... :)
static const struct bind_map bind_maps[2][13] = {
static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
{
{ "input_player1_a", "input_player1_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A },
{ "input_player1_b", "input_player1_b_btn", NULL, SNES_DEVICE_ID_JOYPAD_B },
{ "input_player1_y", "input_player1_y_btn", NULL, SNES_DEVICE_ID_JOYPAD_Y },
{ "input_player1_x", "input_player1_x_btn", NULL, SNES_DEVICE_ID_JOYPAD_X },
{ "input_player1_start", "input_player1_start_btn", NULL, SNES_DEVICE_ID_JOYPAD_START },
{ "input_player1_select", "input_player1_select_btn", NULL, SNES_DEVICE_ID_JOYPAD_SELECT },
{ "input_player1_l", "input_player1_l_btn", NULL, SNES_DEVICE_ID_JOYPAD_L },
{ "input_player1_r", "input_player1_r_btn", NULL, SNES_DEVICE_ID_JOYPAD_R },
{ "input_player1_left", "input_player1_left_btn", "input_player1_left_axis", SNES_DEVICE_ID_JOYPAD_LEFT },
{ "input_player1_right", "input_player1_right_btn", "input_player1_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT },
{ "input_player1_up", "input_player1_up_btn", "input_player1_up_axis", SNES_DEVICE_ID_JOYPAD_UP },
{ "input_player1_down", "input_player1_down_btn", "input_player1_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN },
{ "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SSNES_FAST_FORWARD_KEY }
},
DECLARE_BIND(player1_a, SNES_DEVICE_ID_JOYPAD_A)
DECLARE_BIND(player1_b, SNES_DEVICE_ID_JOYPAD_B)
DECLARE_BIND(player1_y, SNES_DEVICE_ID_JOYPAD_Y)
DECLARE_BIND(player1_x, SNES_DEVICE_ID_JOYPAD_X)
DECLARE_BIND(player1_start, SNES_DEVICE_ID_JOYPAD_START)
DECLARE_BIND(player1_select, SNES_DEVICE_ID_JOYPAD_SELECT)
DECLARE_BIND(player1_l, SNES_DEVICE_ID_JOYPAD_L)
DECLARE_BIND(player1_r, SNES_DEVICE_ID_JOYPAD_R)
DECLARE_BIND(player1_left, SNES_DEVICE_ID_JOYPAD_LEFT)
DECLARE_BIND(player1_right, SNES_DEVICE_ID_JOYPAD_RIGHT)
DECLARE_BIND(player1_up, SNES_DEVICE_ID_JOYPAD_UP)
DECLARE_BIND(player1_down, SNES_DEVICE_ID_JOYPAD_DOWN)
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
},
{
{ "input_player2_a", "input_player2_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A },
{ "input_player2_b", "input_player2_b_btn", NULL, SNES_DEVICE_ID_JOYPAD_B },
{ "input_player2_y", "input_player2_y_btn", NULL, SNES_DEVICE_ID_JOYPAD_Y },
{ "input_player2_x", "input_player2_x_btn", NULL, SNES_DEVICE_ID_JOYPAD_X },
{ "input_player2_start", "input_player2_start_btn", NULL, SNES_DEVICE_ID_JOYPAD_START },
{ "input_player2_select", "input_player2_select_btn", NULL, SNES_DEVICE_ID_JOYPAD_SELECT },
{ "input_player2_l", "input_player2_l_btn", NULL, SNES_DEVICE_ID_JOYPAD_L },
{ "input_player2_r", "input_player2_r_btn", NULL, SNES_DEVICE_ID_JOYPAD_R },
{ "input_player2_left", "input_player2_left_btn", "input_player2_left_axis", SNES_DEVICE_ID_JOYPAD_LEFT },
{ "input_player2_right", "input_player2_right_btn", "input_player2_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT },
{ "input_player2_up", "input_player2_up_btn", "input_player2_up_axis", SNES_DEVICE_ID_JOYPAD_UP },
{ "input_player2_down", "input_player2_down_btn", "input_player2_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN },
{ "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SSNES_FAST_FORWARD_KEY }
}
DECLARE_BIND(player2_a, SNES_DEVICE_ID_JOYPAD_A)
DECLARE_BIND(player2_b, SNES_DEVICE_ID_JOYPAD_B)
DECLARE_BIND(player2_y, SNES_DEVICE_ID_JOYPAD_Y)
DECLARE_BIND(player2_x, SNES_DEVICE_ID_JOYPAD_X)
DECLARE_BIND(player2_start, SNES_DEVICE_ID_JOYPAD_START)
DECLARE_BIND(player2_select, SNES_DEVICE_ID_JOYPAD_SELECT)
DECLARE_BIND(player2_l, SNES_DEVICE_ID_JOYPAD_L)
DECLARE_BIND(player2_r, SNES_DEVICE_ID_JOYPAD_R)
DECLARE_BIND(player2_left, SNES_DEVICE_ID_JOYPAD_LEFT)
DECLARE_BIND(player2_right, SNES_DEVICE_ID_JOYPAD_RIGHT)
DECLARE_BIND(player2_up, SNES_DEVICE_ID_JOYPAD_UP)
DECLARE_BIND(player2_down, SNES_DEVICE_ID_JOYPAD_DOWN)
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
},
{
DECLARE_BIND(player3_a, SNES_DEVICE_ID_JOYPAD_A)
DECLARE_BIND(player3_b, SNES_DEVICE_ID_JOYPAD_B)
DECLARE_BIND(player3_y, SNES_DEVICE_ID_JOYPAD_Y)
DECLARE_BIND(player3_x, SNES_DEVICE_ID_JOYPAD_X)
DECLARE_BIND(player3_start, SNES_DEVICE_ID_JOYPAD_START)
DECLARE_BIND(player3_select, SNES_DEVICE_ID_JOYPAD_SELECT)
DECLARE_BIND(player3_l, SNES_DEVICE_ID_JOYPAD_L)
DECLARE_BIND(player3_r, SNES_DEVICE_ID_JOYPAD_R)
DECLARE_BIND(player3_left, SNES_DEVICE_ID_JOYPAD_LEFT)
DECLARE_BIND(player3_right, SNES_DEVICE_ID_JOYPAD_RIGHT)
DECLARE_BIND(player3_up, SNES_DEVICE_ID_JOYPAD_UP)
DECLARE_BIND(player3_down, SNES_DEVICE_ID_JOYPAD_DOWN)
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
},
{
DECLARE_BIND(player4_a, SNES_DEVICE_ID_JOYPAD_A)
DECLARE_BIND(player4_b, SNES_DEVICE_ID_JOYPAD_B)
DECLARE_BIND(player4_y, SNES_DEVICE_ID_JOYPAD_Y)
DECLARE_BIND(player4_x, SNES_DEVICE_ID_JOYPAD_X)
DECLARE_BIND(player4_start, SNES_DEVICE_ID_JOYPAD_START)
DECLARE_BIND(player4_select, SNES_DEVICE_ID_JOYPAD_SELECT)
DECLARE_BIND(player4_l, SNES_DEVICE_ID_JOYPAD_L)
DECLARE_BIND(player4_r, SNES_DEVICE_ID_JOYPAD_R)
DECLARE_BIND(player4_left, SNES_DEVICE_ID_JOYPAD_LEFT)
DECLARE_BIND(player4_right, SNES_DEVICE_ID_JOYPAD_RIGHT)
DECLARE_BIND(player4_up, SNES_DEVICE_ID_JOYPAD_UP)
DECLARE_BIND(player4_down, SNES_DEVICE_ID_JOYPAD_DOWN)
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
},
{
DECLARE_BIND(player5_a, SNES_DEVICE_ID_JOYPAD_A)
DECLARE_BIND(player5_b, SNES_DEVICE_ID_JOYPAD_B)
DECLARE_BIND(player5_y, SNES_DEVICE_ID_JOYPAD_Y)
DECLARE_BIND(player5_x, SNES_DEVICE_ID_JOYPAD_X)
DECLARE_BIND(player5_start, SNES_DEVICE_ID_JOYPAD_START)
DECLARE_BIND(player5_select, SNES_DEVICE_ID_JOYPAD_SELECT)
DECLARE_BIND(player5_l, SNES_DEVICE_ID_JOYPAD_L)
DECLARE_BIND(player5_r, SNES_DEVICE_ID_JOYPAD_R)
DECLARE_BIND(player5_left, SNES_DEVICE_ID_JOYPAD_LEFT)
DECLARE_BIND(player5_right, SNES_DEVICE_ID_JOYPAD_RIGHT)
DECLARE_BIND(player5_up, SNES_DEVICE_ID_JOYPAD_UP)
DECLARE_BIND(player5_down, SNES_DEVICE_ID_JOYPAD_DOWN)
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
DECLARE_BIND(exit_emulator, SSNES_QUIT_KEY)
DECLARE_BIND(toggle_fullscreen, SSNES_FULLSCREEN_TOGGLE_KEY)
},
};
struct key_map
@ -352,6 +445,7 @@ struct key_map
};
// Edit: Not portable to different input systems atm. Might move this map into the driver itself or something.
// However, this should map nicely over to other systems aswell since the definition are mostly the same anyways.
static const struct key_map sdlk_map[] = {
{ "left", SDLK_LEFT },
{ "right", SDLK_RIGHT },
@ -415,12 +509,12 @@ static int find_sdlk_key(const char *str)
static void read_keybinds(config_file_t *conf)
{
char *tmp_key = NULL;
int tmp_btn;
char *tmp_btn = NULL;
char *tmp_axis = NULL;
for (int j = 0; j < 1; j++)
for (int j = 0; j < MAX_PLAYERS; j++)
{
for (int i = 0; i < sizeof(bind_maps[j])/sizeof(struct bind_map); i++)
for (int i = 0; i < sizeof(bind_maps[0])/sizeof(struct bind_map); i++)
{
struct snes_keybind *bind = find_snes_bind(j, bind_maps[j][i].snes_key);
if (!bind)
@ -437,10 +531,35 @@ static void read_keybinds(config_file_t *conf)
tmp_key = NULL;
}
if (bind_maps[j][i].btn && config_get_int(conf, bind_maps[j][i].btn, &tmp_btn))
if (bind_maps[j][i].btn && config_get_string(conf, bind_maps[j][i].btn, &tmp_btn))
{
if (tmp_btn >= 0)
bind->joykey = tmp_btn;
const char *btn = tmp_btn;
if (*btn++ == 'h')
{
if (isdigit(*btn))
{
char *dir = NULL;
int hat = strtol(btn, &dir, 0);
int hat_dir = 0;
if (dir)
{
if (strcasecmp(dir, "up") == 0)
hat_dir = HAT_UP_MASK;
else if (strcasecmp(dir, "down") == 0)
hat_dir = HAT_DOWN_MASK;
else if (strcasecmp(dir, "left") == 0)
hat_dir = HAT_LEFT_MASK;
else if (strcasecmp(dir, "right") == 0)
hat_dir = HAT_RIGHT_MASK;
if (hat_dir)
bind->joykey = HAT_MAP(hat, hat_dir);
}
}
}
else
bind->joykey = strtol(tmp_btn, NULL, 0);
free(tmp_btn);
}
if (bind_maps[j][i].axis && config_get_string(conf, bind_maps[j][i].axis, &tmp_axis))
@ -459,38 +578,5 @@ static void read_keybinds(config_file_t *conf)
}
}
}
char *tmp_str;
if (config_get_string(conf, "input_toggle_fullscreen", &tmp_str))
{
int key = find_sdlk_key(tmp_str);
if (key >= 0)
g_settings.input.toggle_fullscreen_key = key;
free(tmp_str);
}
if (config_get_string(conf, "input_save_state", &tmp_str))
{
int key = find_sdlk_key(tmp_str);
if (key >= 0)
g_settings.input.save_state_key = key;
free(tmp_str);
}
if (config_get_string(conf, "input_load_state", &tmp_str))
{
int key = find_sdlk_key(tmp_str);
if (key >= 0)
g_settings.input.load_state_key = key;
free(tmp_str);
}
if (config_get_string(conf, "input_exit_emulator", &tmp_str))
{
int key = find_sdlk_key(tmp_str);
if (key >= 0)
g_settings.input.exit_emulator_key = key;
free(tmp_str);
}
}

408
ssnes.c
View File

@ -36,14 +36,11 @@
struct global g_extern = {
.video_active = true,
.audio_active = true,
.game_type = SSNES_CART_NORMAL,
};
// To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then.
#define AUDIO_CHUNK_SIZE_BLOCKING 64
#define AUDIO_CHUNK_SIZE_NONBLOCKING 2048 // So we don't get complete line-noise when fast-forwarding audio.
static size_t audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING;
void set_fast_forward_button(bool new_button_state)
static void set_fast_forward_button(bool new_button_state)
{
static bool old_button_state = false;
static bool syncing_state = false;
@ -54,10 +51,11 @@ void set_fast_forward_button(bool new_button_state)
driver.video->set_nonblock_state(driver.video_data, syncing_state);
if (g_extern.audio_active)
driver.audio->set_nonblock_state(driver.audio_data, (g_settings.audio.sync) ? syncing_state : true);
if (syncing_state)
audio_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING;
g_extern.audio_data.chunk_size = g_extern.audio_data.nonblock_chunk_size;
else
audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING;
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
}
old_button_state = new_button_state;
}
@ -102,7 +100,9 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
#ifdef HAVE_FILTER
uint16_t output_filter[width * height * 4 * 4];
uint16_t output[width * height];
process_frame(output, data, width, height);
if (g_settings.video.filter != FILTER_NONE)
process_frame(output, data, width, height);
switch (g_settings.video.filter)
{
@ -160,37 +160,42 @@ static void audio_sample(uint16_t left, uint16_t right)
}
#endif
static float data[AUDIO_CHUNK_SIZE_NONBLOCKING];
static int data_ptr = 0;
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(*(int16_t*)&left)/0x8000;
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(*(int16_t*)&right)/0x8000;
data[data_ptr++] = (float)(*(int16_t*)&left)/0x7FFF;
data[data_ptr++] = (float)(*(int16_t*)&right)/0x7FFF;
if ( data_ptr >= audio_chunk_size )
if (g_extern.audio_data.data_ptr >= g_extern.audio_data.chunk_size)
{
float outsamples[audio_chunk_size * 16];
int16_t temp_outsamples[audio_chunk_size * 16];
SRC_DATA src_data;
src_data.data_in = data;
src_data.data_out = outsamples;
src_data.input_frames = audio_chunk_size / 2;
src_data.output_frames = audio_chunk_size * 8;
src_data.data_in = g_extern.audio_data.data;
src_data.data_out = g_extern.audio_data.outsamples;
src_data.input_frames = g_extern.audio_data.chunk_size / 2;
src_data.output_frames = g_extern.audio_data.chunk_size * 8;
src_data.end_of_input = 0;
src_data.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate;
src_process(g_extern.source, &src_data);
src_float_to_short_array(outsamples, temp_outsamples, src_data.output_frames_gen * 2);
if ( driver.audio->write(driver.audio_data, temp_outsamples, src_data.output_frames_gen * 4) < 0 )
if (driver.audio->float_samples)
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
g_extern.audio_active = false;
if (driver.audio->write(driver.audio_data, g_extern.audio_data.outsamples, src_data.output_frames_gen * sizeof(float) * 2) < 0)
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
g_extern.audio_active = false;
}
}
else
{
src_float_to_short_array(g_extern.audio_data.outsamples, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * 2);
if (driver.audio->write(driver.audio_data, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * sizeof(int16_t) * 2) < 0)
{
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
g_extern.audio_active = false;
}
}
data_ptr = 0;
g_extern.audio_data.data_ptr = 0;
}
}
@ -201,7 +206,10 @@ static void input_poll(void)
static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id)
{
const struct snes_keybind *binds[] = { g_settings.input.binds[0], g_settings.input.binds[1] };
const struct snes_keybind *binds[MAX_PLAYERS];
for (int i = 0; i < MAX_PLAYERS; i++)
binds[i] = g_settings.input.binds[i];
return driver.input->input_state(driver.input_data, binds, port, device, index, id);
}
@ -234,10 +242,22 @@ static void print_help(void)
puts("=================================================");
puts("ssnes: Simple Super Nintendo Emulator (libsnes)");
puts("=================================================");
puts("Usage: ssnes [rom file] [-h/--help | -s/--save" FFMPEG_HELP_QUARK "]");
puts("Usage: ssnes [rom file] [-h/--help | -c/--config | -v/--verbose | -4/--multitap | -j/--justifier | -J/--justifiers | -S/--savestate | -m/--mouse | -g/--gameboy | -b/--bsx | -B/--bsxslot | --sufamiA | --sufamiB | -p/--scope | -s/--save" FFMPEG_HELP_QUARK "]");
puts("\t-h/--help: Show this help message");
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin");
puts("\t-S/--savestate: Path to use for save states. If not selected, *.state will be assumed.");
puts("\t-c/--config: Path for config file." SSNES_DEFAULT_CONF_PATH_STR);
puts("\t-g/--gameboy: Path to Gameboy ROM. Load SuperGameBoy as the regular rom.");
puts("\t-b/--bsx: Path to BSX rom. Load BSX BIOS as the regular rom.");
puts("\t-B/--bsxslot: Path to BSX slotted rom. Load BSX BIOS as the regular rom.");
puts("\t--sufamiA: Path to A slot of Sufami Turbo. Load Sufami base cart as regular rom.");
puts("\t--sufamiB: Path to B slot of Sufami Turbo.");
puts("\t-m/--mouse: Connect a virtual mouse into designated port of the SNES (1 or 2).");
puts("\t\tThis argument can be specified several times to connect more mice.");
puts("\t-p/--scope: Connect a virtual SuperScope into port 2 of the SNES.");
puts("\t-j/--justifier: Connect a virtual Konami Justifier into port 2 of the SNES.");
puts("\t-J/--justifiers: Daisy chain two virtual Konami Justifiers into port 2 of the SNES.");
puts("\t-4/--multitap: Connect a multitap to port 2 of the SNES.");
#ifdef HAVE_FFMPEG
puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file.");
@ -260,7 +280,18 @@ static void parse_input(int argc, char *argv[])
{ "record", 1, NULL, 'r' },
#endif
{ "verbose", 0, NULL, 'v' },
{ "gameboy", 1, NULL, 'g' },
{ "config", 0, NULL, 'c' },
{ "mouse", 1, NULL, 'm' },
{ "scope", 0, NULL, 'p' },
{ "savestate", 1, NULL, 'S' },
{ "bsx", 1, NULL, 'b' },
{ "bsxslot", 1, NULL, 'B' },
{ "justifier", 0, NULL, 'j' },
{ "justifiers", 0, NULL, 'J' },
{ "multitap", 0, NULL, '4' },
{ "sufamiA", 1, NULL, 'Y' },
{ "sufamiB", 1, NULL, 'Z' },
{ NULL, 0, NULL, 0 }
};
@ -272,10 +303,11 @@ static void parse_input(int argc, char *argv[])
#define FFMPEG_RECORD_ARG
#endif
char optstring[] = "hs:vc:" FFMPEG_RECORD_ARG;
char optstring[] = "hs:vc:S:m:p4jJg:b:B:Y:Z:" FFMPEG_RECORD_ARG;
for(;;)
{
int c = getopt_long(argc, argv, optstring, opts, &option_index);
int port;
if (c == -1)
break;
@ -286,15 +318,70 @@ static void parse_input(int argc, char *argv[])
print_help();
exit(0);
case '4':
g_extern.has_multitap = true;
break;
case 'j':
g_extern.has_justifier = true;
break;
case 'J':
g_extern.has_justifiers = true;
break;
case 's':
strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm));
g_extern.savefile_name_srm[sizeof(g_extern.savefile_name_srm)-1] = '\0';
strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm) - 1);
break;
case 'g':
strncpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path) - 1);
g_extern.game_type = SSNES_CART_SGB;
break;
case 'b':
strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1);
g_extern.game_type = SSNES_CART_BSX;
break;
case 'B':
strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1);
g_extern.game_type = SSNES_CART_BSX_SLOTTED;
break;
case 'Y':
strncpy(g_extern.sufami_rom_path[0], optarg, sizeof(g_extern.sufami_rom_path[0]) - 1);
g_extern.game_type = SSNES_CART_SUFAMI;
break;
case 'Z':
strncpy(g_extern.sufami_rom_path[1], optarg, sizeof(g_extern.sufami_rom_path[1]) - 1);
g_extern.game_type = SSNES_CART_SUFAMI;
break;
case 'S':
strncpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name) - 1);
break;
case 'v':
g_extern.verbose = true;
break;
case 'm':
port = strtol(optarg, NULL, 0);
if (port < 1 || port > 2)
{
SSNES_ERR("Connect mouse to port 1 or 2.\n");
print_help();
exit(1);
}
g_extern.has_mouse[port - 1] = true;
break;
case 'p':
g_extern.has_scope[1] = true;
break;
case 'c':
strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1);
break;
@ -329,76 +416,129 @@ static void parse_input(int argc, char *argv[])
g_extern.rom_file = fopen(argv[optind], "rb");
if (g_extern.rom_file == NULL)
{
SSNES_ERR("Could not open file: \"%s\"\n", optarg);
SSNES_ERR("Could not open file: \"%s\"\n", argv[optind]);
exit(1);
}
// strl* would be nice :D
if (strlen(g_extern.savefile_name_srm) == 0)
fill_pathname(g_extern.savefile_name_srm, argv[optind], ".srm");
{
strcpy(g_extern.savefile_name_srm, g_extern.basename);
size_t len = strlen(g_extern.savefile_name_srm);
strncat(g_extern.savefile_name_srm, ".srm", sizeof(g_extern.savefile_name_srm) - len - 1);
}
if (strlen(g_extern.savestate_name) == 0)
{
strcpy(g_extern.savestate_name, g_extern.basename);
size_t len = strlen(g_extern.savestate_name);
strncat(g_extern.savestate_name, ".state", sizeof(g_extern.savestate_name) - len - 1);
}
}
else if (strlen(g_extern.savefile_name_srm) == 0)
{
SSNES_ERR("Need savefile argument when reading rom from stdin.\n");
SSNES_ERR("Need savefile path argument (--save) when reading rom from stdin.\n");
print_help();
exit(1);
}
else if (strlen(g_extern.savestate_name) == 0)
{
SSNES_ERR("Need savestate path argument (--savefile) when reading rom from stdin.\n");
print_help();
exit(1);
}
}
int main(int argc, char *argv[])
// TODO: Add rest of the controllers.
static void init_controllers(void)
{
parse_input(argc, argv);
parse_config();
init_dlsym();
psnes_init();
if (strlen(g_extern.basename) > 0)
psnes_set_cartridge_basename(g_extern.basename);
SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor());
void *rom_buf;
ssize_t rom_len = 0;
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
if (g_extern.has_justifier)
{
SSNES_ERR("Could not read ROM file.\n");
exit(1);
SSNES_LOG("Connecting Justifier to port 2.\n");
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_JUSTIFIER);
}
SSNES_LOG("ROM size: %d bytes\n", (int)rom_len);
if (g_extern.rom_file != NULL)
fclose(g_extern.rom_file);
char statefile_name[strlen(g_extern.savefile_name_srm)+strlen(".state")+1];
char savefile_name_rtc[strlen(g_extern.savefile_name_srm)+strlen(".rtc")+1];
fill_pathname(statefile_name, argv[1], ".state");
fill_pathname(savefile_name_rtc, argv[1], ".rtc");
init_drivers();
psnes_set_video_refresh(video_frame);
psnes_set_audio_sample(audio_sample);
psnes_set_input_poll(input_poll);
psnes_set_input_state(input_state);
if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len))
else if (g_extern.has_justifiers)
{
SSNES_ERR("ROM file is not valid!\n");
goto error;
SSNES_LOG("Connecting Justifiers to port 2.\n");
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_JUSTIFIERS);
}
free(rom_buf);
unsigned serial_size = psnes_serialize_size();
uint8_t *serial_data = malloc(serial_size);
if (serial_data == NULL)
else if (g_extern.has_multitap)
{
SSNES_ERR("Failed to allocate memory for states!\n");
goto error;
SSNES_LOG("Connecting multitap to port 2.\n");
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_MULTITAP);
}
else
{
for (int i = 0; i < 2; i++)
{
if (g_extern.has_mouse[i])
{
SSNES_LOG("Connecting mouse to port %d\n", i + 1);
psnes_set_controller_port_device(i, SNES_DEVICE_MOUSE);
}
else if (g_extern.has_scope[i])
{
SSNES_LOG("Connecting scope to port %d\n", i + 1);
psnes_set_controller_port_device(i, SNES_DEVICE_SUPER_SCOPE);
}
}
}
}
static inline void load_save_files(void)
{
switch (g_extern.game_type)
{
case SSNES_CART_NORMAL:
case SSNES_CART_SGB:
load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
load_save_file(g_extern.savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
break;
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
load_save_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM);
break;
case SSNES_CART_SUFAMI:
load_save_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM);
load_save_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM);
break;
default:
break;
}
}
static inline void save_files(void)
{
switch (g_extern.game_type)
{
case SSNES_CART_NORMAL:
case SSNES_CART_SGB:
save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
save_file(g_extern.savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
break;
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
save_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
save_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM);
break;
case SSNES_CART_SUFAMI:
save_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM);
save_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM);
break;
default:
break;
}
}
load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
load_save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
#ifdef HAVE_FFMPEG
static void init_recording(void)
{
// Hardcode these options at the moment. Should be specificed in the config file later on.
if (g_extern.recording)
{
@ -413,7 +553,7 @@ int main(int argc, char *argv[])
.channels = 2,
.samplerate = 32040,
.filename = g_extern.record_path,
.fps = snes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
.fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
.aspect_ratio = 4.0/3
};
SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path);
@ -424,42 +564,116 @@ int main(int argc, char *argv[])
g_extern.recording = false;
}
}
}
static void deinit_recording(void)
{
if (g_extern.recording)
{
ffemu_finalize(g_extern.rec);
ffemu_free(g_extern.rec);
}
}
#endif
static void fill_pathnames(void)
{
switch (g_extern.game_type)
{
case SSNES_CART_BSX:
case SSNES_CART_BSX_SLOTTED:
// BSX PSRM
fill_pathname(g_extern.savefile_name_psrm, g_extern.savefile_name_srm, ".psrm");
break;
case SSNES_CART_SUFAMI:
// SUFAMI ARAM
fill_pathname(g_extern.savefile_name_asrm, g_extern.savefile_name_srm, ".asrm");
// SUFAMI BRAM
fill_pathname(g_extern.savefile_name_bsrm, g_extern.savefile_name_srm, ".bsrm");
break;
default:
// Infer .rtc save path from save ram path.
fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc");
}
}
int main(int argc, char *argv[])
{
parse_input(argc, argv);
parse_config();
init_dlsym();
psnes_init();
if (strlen(g_extern.basename) > 0)
psnes_set_cartridge_basename(g_extern.basename);
SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor());
fill_pathnames();
if (!init_rom_file(g_extern.game_type))
goto error;
init_drivers();
psnes_set_video_refresh(video_frame);
psnes_set_audio_sample(audio_sample);
psnes_set_input_poll(input_poll);
psnes_set_input_state(input_state);
init_controllers();
unsigned serial_size = psnes_serialize_size();
uint8_t *serial_data = malloc(serial_size);
if (serial_data == NULL)
{
SSNES_ERR("Failed to allocate memory for states!\n");
goto error;
}
load_save_files();
#ifdef HAVE_FFMPEG
init_recording();
#endif
// Main loop
for(;;)
{
if (driver.input->key_pressed(driver.input_data, g_settings.input.exit_emulator_key) ||
// Time to drop?
if (driver.input->key_pressed(driver.input_data, SSNES_QUIT_KEY) ||
!driver.video->alive(driver.video_data))
break;
if (driver.input->key_pressed(driver.input_data, g_settings.input.save_state_key))
{
write_file(statefile_name, serial_data, serial_size);
}
set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY));
else if (driver.input->key_pressed(driver.input_data, g_settings.input.load_state_key))
load_state(statefile_name, serial_data, serial_size);
// Save or load state here.
if (driver.input->key_pressed(driver.input_data, SSNES_SAVE_STATE_KEY))
write_file(g_extern.savestate_name, serial_data, serial_size);
else if (driver.input->key_pressed(driver.input_data, SSNES_LOAD_STATE_KEY))
load_state(g_extern.savestate_name, serial_data, serial_size);
else if (driver.input->key_pressed(driver.input_data, g_settings.input.toggle_fullscreen_key))
// If we go fullscreen we drop all drivers and reinit to be safe.
else if (driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY))
{
g_settings.video.fullscreen = !g_settings.video.fullscreen;
uninit_drivers();
init_drivers();
}
// Run libsnes for one frame.
psnes_run();
}
#ifdef HAVE_FFMPEG
if (g_extern.recording)
{
ffemu_finalize(g_extern.rec);
ffemu_free(g_extern.rec);
}
deinit_recording();
#endif
save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
// Flush out SRAM (and RTC)
save_files();
psnes_unload_cartridge();
psnes_term();

View File

@ -88,7 +88,13 @@
# input_player1_up = up
# input_player1_down = down
# If desired, it is possible to override which joypads are being used for player 1 and 2. First joypad available is 0.
# input_player1_joypad_index = 0
# input_player2_joypad_index = 1
# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output.
# You can use joypad hats with hnxx, where n is the hat, and xx is a string representing direction.
# E.g. "h0up"
# input_player1_a_btn = 1
# input_player1_b_btn = 0
# input_player1_y_btn = 2
@ -102,7 +108,9 @@
# input_player1_up_btn = 13
# input_player1_down_btn = 14
# Axis for DPAD. Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number.
# Axis for DPAD.
# Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number.
# Do note that every other input option has the corresponding _btn and _axis binds as well; they are omitted here for clarity.
# input_player1_left_axis = -0
# input_player1_right_axis = +0
# input_player1_up_axis = +1
@ -138,8 +146,8 @@
# input_player2_left_axis = -0
# input_player2_right_axis = +0
# input_player2_up_axis = +1
# input_player2_down_axis = -1
# input_player2_up_axis = -1
# input_player2_down_axis = +1
# Toggles fullscreen.
# input_toggle_fullscreen = f
@ -150,8 +158,7 @@
# Toggles between fast-forwarding and normal speed.
# input_toggle_fast_forward = space
# Same, just mapping to a joypad button.
# input_toggle_fast_forward_btn = 10
# Key to exit emulator cleanly.
# Key to exit emulator cleanly.
# Killing it in any hard way (SIGTERM, SIGKILL, etc, will terminate emulator without saving RAM, etc.)
# input_exit_emulator = escape

6
tools/main-stub.c Normal file
View File

@ -0,0 +1,6 @@
int real_main(int argc, char *argv[]);
int main(int argc, char *argv[])
{
return real_main(argc, argv);
}

284
tools/ssnes-joyconfig.c Normal file
View File

@ -0,0 +1,284 @@
/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes.
* Copyright (C) 2010 - Hans-Kristian Arntzen
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SDL
#include "SDL.h"
#endif
#include "conf/config_file.h"
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include "general.h"
static int g_player = 1;
static int g_joypad = 1;
static char *g_in_path = NULL;
static char *g_out_path = NULL;
static void print_help(void)
{
puts("==================");
puts("ssnes-joyconfig");
puts("==================");
puts("Usage: ssnes-joyconfig [ -p/--player <1|2> | -j/--joypad <num> | -i/--input <file> | -o/--output <file> | -h/--help ]");
puts("");
puts("-p/--player: Which player to configure for (1 or 2).");
puts("-j/--joypad: Which joypad to use when configuring (first joypad is 1).");
puts("-i/--input: Input file to configure with. Binds will be added on or overwritten.");
puts("\tIf not selected, an empty config will be used as a base.");
puts("-o/--output: Output file to write to. If not selected, config file will be dumped to stdout.");
puts("-h/--help: This help.");
}
struct bind
{
char *keystr;
char *confbtn[MAX_PLAYERS];
char *confaxis[MAX_PLAYERS];
};
#define BIND(x, k) { x, { "input_player1_" #k "_btn", "input_player2_" #k "_btn", "input_player3_" #k "_btn", "input_player4_" #k "_btn", "input_player5_" #k "_btn" }, {"input_player1_" #k "_axis", "input_player2_" #k "_axis", "input_player3_" #k "_axis", "input_player4_" #k "_axis", "input_player5_" #k "_axis"}},
static struct bind binds[] = {
BIND("A button (right)", a)
BIND("B button (down)", b)
BIND("X button (top)", x)
BIND("Y button (left)", y)
BIND("L button (left shoulder)", l)
BIND("R button (right shoulder)", r)
BIND("Start button", start)
BIND("Select button", select)
BIND("Left D-pad", left)
BIND("Up D-pad", up)
BIND("Right D-pad", right)
BIND("Down D-pad", down)
};
void get_binds(config_file_t *conf, int player, int joypad)
{
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO) < 0)
{
fprintf(stderr, "Failed to init joystick subsystem.\n");
exit(1);
}
SDL_Joystick *joystick;
int num = SDL_NumJoysticks();
if (joypad >= num)
{
fprintf(stderr, "Cannot find joystick number %d, only have %d joysticks available ...\n", joypad + 1, num);
exit(1);
}
joystick = SDL_JoystickOpen(joypad);
if (!joystick)
{
fprintf(stderr, "Cannot open joystick.\n");
exit(1);
}
SDL_JoystickUpdate();
int last_axis = 0xFF;
int num_axes = SDL_JoystickNumAxes(joystick);
int initial_axes[num_axes];
for (int i = 0; i < num_axes; i++)
initial_axes[i] = SDL_JoystickGetAxis(joystick, i);
fprintf(stderr, "Configuring binds for player #%d on joypad #%d (%s)\n", player + 1, joypad + 1, SDL_JoystickName(joypad));
fprintf(stderr, "Press Ctrl-C to exit early.\n");
fprintf(stderr, "\n");
for (unsigned i = 0; i < sizeof(binds) / sizeof(struct bind); i++)
{
fprintf(stderr, "%s\n", binds[i].keystr);
bool done = false;
SDL_Event event;
int value;
const char *quark;
while (SDL_WaitEvent(&event) && !done)
{
switch (event.type)
{
case SDL_JOYBUTTONDOWN:
fprintf(stderr, "\tJoybutton pressed: %d\n", (int)event.jbutton.button);
done = true;
config_set_int(conf, binds[i].confbtn[player], event.jbutton.button);
break;
case SDL_JOYAXISMOTION:
if (abs(event.jaxis.value) > 20000 &&
abs((int)event.jaxis.value - initial_axes[event.jaxis.axis]) > 20000 &&
event.jaxis.axis != last_axis)
{
last_axis = event.jaxis.axis;
fprintf(stderr, "\tJoyaxis moved: Axis %d, Value %d\n", (int)event.jaxis.axis, (int)event.jaxis.value);
done = true;
char buf[8];
snprintf(buf, sizeof(buf), event.jaxis.value > 0 ? "+%d" : "-%d", event.jaxis.axis);
config_set_string(conf, binds[i].confaxis[player], buf);
}
break;
case SDL_JOYHATMOTION:
value = event.jhat.value;
if (value & SDL_HAT_UP)
quark = "up";
else if (value & SDL_HAT_DOWN)
quark = "down";
else if (value & SDL_HAT_LEFT)
quark = "left";
else if (value & SDL_HAT_RIGHT)
quark = "right";
else
break;
fprintf(stderr, "\tJoyhat moved: Hat %d, direction %s\n", (int)event.jhat.hat, quark);
done = true;
char buf[16];
snprintf(buf, sizeof(buf), "h%d%s", event.jhat.hat, quark);
config_set_string(conf, binds[i].confbtn[player], buf);
break;
case SDL_QUIT:
goto end;
default:
break;
}
}
}
end:
SDL_JoystickClose(joystick);
SDL_Quit();
}
static void parse_input(int argc, char *argv[])
{
char optstring[] = "i:o:p:j:h";
struct option opts[] = {
{ "input", 1, NULL, 'i' },
{ "output", 1, NULL, 'o' },
{ "player", 1, NULL, 'p' },
{ "joypad", 1, NULL, 'j' },
{ "help", 0, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
int option_index = 0;
for(;;)
{
int c = getopt_long(argc, argv, optstring, opts, &option_index);
if (c == -1)
break;
switch (c)
{
case 'h':
print_help();
exit(0);
case 'i':
g_in_path = strdup(optarg);
break;
case 'o':
g_out_path = strdup(optarg);
break;
case 'j':
g_joypad = strtol(optarg, NULL, 0);
if (g_joypad < 1)
{
fprintf(stderr, "Joypad number can't be less than 1!\n");
exit(1);
}
break;
case 'p':
g_player = strtol(optarg, NULL, 0);
if (g_player < 1)
{
fprintf(stderr, "Player number must be at least 1!\n");
exit(1);
}
else if (g_player > MAX_PLAYERS)
{
fprintf(stderr, "Player number must be from 1 to %d.\n", MAX_PLAYERS);
exit(1);
}
break;
default:
break;
}
}
if (optind < argc)
{
print_help();
exit(1);
}
}
// Windows is being bitchy. Cannot include SDL.h with a file that has main() it seems ... It simply won't run at all even with -lSDLmain.
#ifdef _WIN32
int real_main(int argc, char *argv[])
#else
int main(int argc, char *argv[])
#endif
{
parse_input(argc, argv);
config_file_t *conf = config_file_new(g_in_path);
if (!conf)
{
fprintf(stderr, "Couldn't open config file ...\n");
return 1;
}
const char *index_list[] = {
"input_player1_joypad_index",
"input_player2_joypad_index",
"input_player3_joypad_index",
"input_player4_joypad_index",
"input_player5_joypad_index"
};
config_set_int(conf, index_list[g_player - 1], g_joypad - 1);
get_binds(conf, g_player - 1, g_joypad - 1);
config_file_write(conf, g_out_path);
config_file_free(conf);
if (g_in_path)
free(g_in_path);
if (g_out_path)
free(g_out_path);
return 0;
}