Merge git://github.com/Themaister/SSNES
This commit is contained in:
commit
956d03ba03
27
Makefile
27
Makefile
|
@ -1,8 +1,10 @@
|
|||
include config.mk
|
||||
|
||||
TARGET = ssnes
|
||||
TARGET = ssnes tools/ssnes-joyconfig
|
||||
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o
|
||||
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o
|
||||
HEADERS = $(wildcard */*.h) $(wildcard *.h)
|
||||
|
||||
LIBS =
|
||||
DEFINES = -DHAVE_CONFIG_H
|
||||
|
@ -74,6 +76,10 @@ else
|
|||
LIBS += $(libsnes)
|
||||
endif
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
CFLAGS = -Wall -O3 -g -std=gnu99 -I.
|
||||
|
||||
all: $(TARGET) config.mk
|
||||
|
@ -83,17 +89,24 @@ config.mk: configure qb/*
|
|||
@exit 1
|
||||
|
||||
ssnes: $(OBJ)
|
||||
$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
%.o: %.c config.h config.mk
|
||||
$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
|
||||
tools/ssnes-joyconfig: $(JOYCONFIG_OBJ)
|
||||
$(Q)$(CC) -o $@ $(JOYCONFIG_OBJ) $(SDL_LIBS) $(LDFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
%.o: %.c config.h config.mk $(HEADERS)
|
||||
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
|
||||
@$(if $(Q), $(shell echo echo CC $<),)
|
||||
|
||||
install: $(TARGET)
|
||||
install -m755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
|
||||
install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg
|
||||
|
||||
uninstall: $(TARGET)
|
||||
rm -rf $(DESTDIR)/$(PREFIX)/bin/$(TARGET)
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/{ssnes,ssnes-joyconfig}
|
||||
rm -f $(DESTDIR)/etc/ssnes.cfg
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
|
@ -103,6 +116,8 @@ clean:
|
|||
rm -f record/*.o
|
||||
rm -f hqflt/*.o
|
||||
rm -f hqflt/snes_ntsc/*.o
|
||||
rm -f input/*.o
|
||||
rm -f tools/*.o
|
||||
rm -f $(TARGET)
|
||||
|
||||
.PHONY: all install uninstall clean
|
||||
|
|
|
@ -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
|
||||
|
|
121
audio/jack.c
121
audio/jack.c
|
@ -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"
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
99
config.def.h
99
config.def.h
|
@ -139,31 +139,29 @@ static const bool audio_sync = true;
|
|||
// How far an axis must be tilted to result in a button press
|
||||
#define AXIS_THRESHOLD 0.5
|
||||
|
||||
#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF)
|
||||
#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U)
|
||||
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
|
||||
|
||||
// To figure out which joypad buttons to use, check jstest or similar.
|
||||
// Axes are configured using the axis number for the positive (up, right)
|
||||
// direction and the number's two's-complement (~) for negative directions.
|
||||
// To use the axis, set the button to -1.
|
||||
// SDL sometimes reverses the axes for some odd reason, but hey. :D
|
||||
|
||||
// Player 1
|
||||
static const struct snes_keybind snes_keybinds_1[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE },
|
||||
{ SSNES_SAVE_STATE_KEY, SDLK_F2, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_LOAD_STATE_KEY, SDLK_F4, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FULLSCREEN_TOGGLE_KEY, SDLK_f, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_QUIT_KEY, SDLK_ESCAPE, NO_BTN, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
|
@ -178,21 +176,66 @@ static const struct snes_keybind snes_keybinds_2[] = {
|
|||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
///// Save state
|
||||
#define SAVE_STATE_KEY SDLK_F2
|
||||
///// Load state
|
||||
#define LOAD_STATE_KEY SDLK_F4
|
||||
// Player 3
|
||||
static const struct snes_keybind snes_keybinds_3[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
//// Toggles between fullscreen and windowed mode.
|
||||
#define TOGGLE_FULLSCREEN SDLK_f
|
||||
// Player 4
|
||||
static const struct snes_keybind snes_keybinds_4[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
// Player 5
|
||||
static const struct snes_keybind snes_keybinds_5[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
26
driver.c
26
driver.c
|
@ -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)
|
||||
|
|
28
driver.h
28
driver.h
|
@ -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);
|
||||
|
|
29
dynamic.c
29
dynamic.c
|
@ -52,6 +52,23 @@ unsigned (*psnes_library_revision_minor)(void);
|
|||
unsigned (*psnes_library_revision_major)(void);
|
||||
|
||||
bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned);
|
||||
bool (*psnes_load_cartridge_super_game_boy)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
bool (*psnes_load_cartridge_bsx)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
bool (*psnes_load_cartridge_bsx_slotted)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
bool (*psnes_load_cartridge_sufami_turbo)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
|
||||
void (*psnes_set_controller_port_device)(bool, unsigned);
|
||||
|
||||
bool (*psnes_get_region)(void);
|
||||
|
||||
unsigned (*psnes_serialize_size)(void);
|
||||
bool (*psnes_serialize)(uint8_t*, unsigned);
|
||||
|
@ -84,7 +101,13 @@ static void load_dynamic(void)
|
|||
SYM(snes_library_revision_minor);
|
||||
SYM(snes_library_revision_major);
|
||||
SYM(snes_run);
|
||||
SYM(snes_get_region);
|
||||
SYM(snes_load_cartridge_normal);
|
||||
SYM(snes_load_cartridge_super_game_boy);
|
||||
SYM(snes_load_cartridge_bsx);
|
||||
SYM(snes_load_cartridge_bsx_slotted);
|
||||
SYM(snes_load_cartridge_sufami_turbo);
|
||||
SYM(snes_set_controller_port_device);
|
||||
SYM(snes_serialize_size);
|
||||
SYM(snes_serialize);
|
||||
SYM(snes_unserialize);
|
||||
|
@ -111,7 +134,13 @@ static void set_statics(void)
|
|||
SSYM(snes_library_revision_minor);
|
||||
SSYM(snes_library_revision_major);
|
||||
SSYM(snes_run);
|
||||
SSYM(snes_get_region);
|
||||
SSYM(snes_load_cartridge_normal);
|
||||
SSYM(snes_load_cartridge_super_game_boy);
|
||||
SSYM(snes_load_cartridge_bsx);
|
||||
SSYM(snes_load_cartridge_bsx_slotted);
|
||||
SSYM(snes_load_cartridge_sufami_turbo);
|
||||
SSYM(snes_set_controller_port_device);
|
||||
SSYM(snes_serialize_size);
|
||||
SSYM(snes_serialize);
|
||||
SSYM(snes_unserialize);
|
||||
|
|
18
dynamic.h
18
dynamic.h
|
@ -35,6 +35,24 @@ extern unsigned (*psnes_library_revision_minor)(void);
|
|||
extern unsigned (*psnes_library_revision_major)(void);
|
||||
|
||||
extern bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned);
|
||||
extern bool (*psnes_load_cartridge_super_game_boy)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
extern bool (*psnes_load_cartridge_bsx)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
extern bool (*psnes_load_cartridge_bsx_slotted)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
extern bool (*psnes_load_cartridge_sufami_turbo)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
|
||||
|
||||
extern void (*psnes_set_controller_port_device)(bool, unsigned);
|
||||
|
||||
extern bool (*psnes_get_region)(void);
|
||||
|
||||
extern unsigned (*psnes_serialize_size)(void);
|
||||
extern bool (*psnes_serialize)(uint8_t*, unsigned);
|
||||
|
|
258
file.c
258
file.c
|
@ -165,3 +165,261 @@ void save_file(const char* path, int type)
|
|||
if ( data && size > 0 )
|
||||
write_file(path, data, size);
|
||||
}
|
||||
|
||||
static bool load_sgb_rom(void)
|
||||
{
|
||||
void *rom_buf = NULL;
|
||||
ssize_t rom_len = 0;
|
||||
|
||||
FILE *extra_rom = NULL;
|
||||
void *extra_rom_buf = NULL;
|
||||
ssize_t extra_rom_len = 0;
|
||||
|
||||
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
|
||||
{
|
||||
SSNES_ERR("Could not read ROM file.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
extra_rom = fopen(g_extern.gb_rom_path, "rb");
|
||||
if (!extra_rom)
|
||||
{
|
||||
SSNES_ERR("Couldn't open GameBoy ROM!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((extra_rom_len = read_file(extra_rom, &extra_rom_buf)) == -1)
|
||||
{
|
||||
SSNES_ERR("Cannot read GameBoy rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!psnes_load_cartridge_super_game_boy(
|
||||
NULL, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load SGB/GameBoy rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
if (extra_rom)
|
||||
fclose(extra_rom);
|
||||
free(rom_buf);
|
||||
free(extra_rom_buf);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
if (extra_rom)
|
||||
fclose(extra_rom);
|
||||
free(rom_buf);
|
||||
free(extra_rom_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool load_bsx_rom(bool slotted)
|
||||
{
|
||||
void *rom_buf = NULL;
|
||||
ssize_t rom_len = 0;
|
||||
|
||||
FILE *extra_rom = NULL;
|
||||
void *extra_rom_buf = NULL;
|
||||
ssize_t extra_rom_len = 0;
|
||||
|
||||
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
|
||||
{
|
||||
SSNES_ERR("Could not read ROM file.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
extra_rom = fopen(g_extern.bsx_rom_path, "rb");
|
||||
if (!extra_rom)
|
||||
{
|
||||
SSNES_ERR("Couldn't open BSX game rom!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((extra_rom_len = read_file(extra_rom, &extra_rom_buf)) == -1)
|
||||
{
|
||||
SSNES_ERR("Cannot read BSX game rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (slotted)
|
||||
{
|
||||
if (!psnes_load_cartridge_bsx_slotted(
|
||||
NULL, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load BSX slotted rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!psnes_load_cartridge_bsx(
|
||||
NULL, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load BSX rom.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
if (extra_rom)
|
||||
fclose(extra_rom);
|
||||
free(rom_buf);
|
||||
free(extra_rom_buf);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
if (extra_rom)
|
||||
fclose(extra_rom);
|
||||
free(rom_buf);
|
||||
free(extra_rom_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool load_sufami_rom(void)
|
||||
{
|
||||
void *rom_buf = NULL;
|
||||
ssize_t rom_len = 0;
|
||||
|
||||
FILE *extra_rom[2] = {NULL};
|
||||
void *extra_rom_buf[2] = {NULL};
|
||||
ssize_t extra_rom_len[2] = {0};
|
||||
|
||||
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
|
||||
{
|
||||
SSNES_ERR("Could not read ROM file.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
const char *roms[2] = { g_extern.sufami_rom_path[0], g_extern.sufami_rom_path[1] };
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (strlen(roms[i]) > 0)
|
||||
{
|
||||
extra_rom[i] = fopen(roms[i], "rb");
|
||||
if (!extra_rom[i])
|
||||
{
|
||||
SSNES_ERR("Couldn't open BSX game rom!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((extra_rom_len[i] = read_file(extra_rom[i], &extra_rom_buf[i])) == -1)
|
||||
{
|
||||
SSNES_ERR("Cannot read BSX game rom.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!psnes_load_cartridge_sufami_turbo(
|
||||
NULL, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf[0], extra_rom_len[0],
|
||||
NULL, extra_rom_buf[1], extra_rom_len[1]))
|
||||
{
|
||||
SSNES_ERR("Cannot load Sufami Turbo rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (extra_rom[i])
|
||||
fclose(extra_rom[i]);
|
||||
free(extra_rom_buf[i]);
|
||||
}
|
||||
free(rom_buf);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (extra_rom[i])
|
||||
fclose(extra_rom[i]);
|
||||
free(extra_rom_buf[i]);
|
||||
}
|
||||
free(rom_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool load_normal_rom(void)
|
||||
{
|
||||
void *rom_buf = NULL;
|
||||
ssize_t rom_len = 0;
|
||||
|
||||
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
|
||||
{
|
||||
SSNES_ERR("Could not read ROM file.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_extern.rom_file != NULL)
|
||||
fclose(g_extern.rom_file);
|
||||
|
||||
SSNES_LOG("ROM size: %d bytes\n", (int)rom_len);
|
||||
|
||||
if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len))
|
||||
{
|
||||
SSNES_ERR("ROM file is not valid!\n");
|
||||
free(rom_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
free(rom_buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool init_rom_file(enum ssnes_game_type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SSNES_CART_SGB:
|
||||
if (!load_sgb_rom())
|
||||
return false;
|
||||
break;
|
||||
|
||||
case SSNES_CART_NORMAL:
|
||||
if (!load_normal_rom())
|
||||
return false;
|
||||
break;
|
||||
|
||||
case SSNES_CART_BSX:
|
||||
if (!load_bsx_rom(false))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case SSNES_CART_BSX_SLOTTED:
|
||||
if (!load_bsx_rom(true))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case SSNES_CART_SUFAMI:
|
||||
if (!load_sufami_rom())
|
||||
return false;
|
||||
break;
|
||||
|
||||
default:
|
||||
SSNES_ERR("Invalid ROM type!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
4
file.h
4
file.h
|
@ -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
|
||||
|
|
51
general.h
51
general.h
|
@ -33,8 +33,9 @@
|
|||
#endif
|
||||
|
||||
|
||||
#define MAX_PLAYERS 2
|
||||
#define MAX_BINDS 14
|
||||
#define MAX_PLAYERS 5
|
||||
#define MAX_BINDS 18 // Needs to be increased every time there are new binds added.
|
||||
#define SSNES_NO_JOYPAD 0xFFFF
|
||||
struct settings
|
||||
{
|
||||
struct
|
||||
|
@ -70,16 +71,22 @@ struct settings
|
|||
{
|
||||
char driver[32];
|
||||
struct snes_keybind binds[MAX_PLAYERS][MAX_BINDS];
|
||||
int save_state_key;
|
||||
int load_state_key;
|
||||
int toggle_fullscreen_key;
|
||||
int exit_emulator_key;
|
||||
float axis_threshold;
|
||||
unsigned joypad_map[MAX_PLAYERS];
|
||||
} input;
|
||||
|
||||
char libsnes[256];
|
||||
};
|
||||
|
||||
enum ssnes_game_type
|
||||
{
|
||||
SSNES_CART_NORMAL = 0,
|
||||
SSNES_CART_SGB,
|
||||
SSNES_CART_BSX,
|
||||
SSNES_CART_BSX_SLOTTED,
|
||||
SSNES_CART_SUFAMI,
|
||||
};
|
||||
|
||||
struct global
|
||||
{
|
||||
bool verbose;
|
||||
|
@ -87,10 +94,40 @@ struct global
|
|||
bool audio_active;
|
||||
bool video_active;
|
||||
|
||||
bool has_mouse[2];
|
||||
bool has_scope[2];
|
||||
bool has_justifier;
|
||||
bool has_justifiers;
|
||||
bool has_multitap;
|
||||
|
||||
FILE *rom_file;
|
||||
char savefile_name_srm[256];
|
||||
enum ssnes_game_type game_type;
|
||||
|
||||
char gb_rom_path[256];
|
||||
char bsx_rom_path[256];
|
||||
char sufami_rom_path[2][256];
|
||||
|
||||
char config_path[256];
|
||||
|
||||
char basename[256];
|
||||
char savefile_name_srm[256];
|
||||
char savefile_name_rtc[512]; // Make sure that fill_pathname has space.
|
||||
char savefile_name_psrm[512];
|
||||
char savefile_name_asrm[512];
|
||||
char savefile_name_bsrm[512];
|
||||
char savestate_name[256];
|
||||
|
||||
struct
|
||||
{
|
||||
float *data;
|
||||
size_t data_ptr;
|
||||
size_t chunk_size;
|
||||
size_t nonblock_chunk_size;
|
||||
size_t block_chunk_size;
|
||||
|
||||
float *outsamples;
|
||||
int16_t *conv_outsamples;
|
||||
} audio_data;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
ffemu_t *rec;
|
||||
|
|
40
gfx/gl.c
40
gfx/gl.c
|
@ -49,6 +49,8 @@
|
|||
#include "shader_glsl.h"
|
||||
#endif
|
||||
|
||||
#include "gl_common.h"
|
||||
|
||||
static const GLfloat vertexes[] = {
|
||||
0, 0, 0,
|
||||
0, 1, 0,
|
||||
|
@ -64,7 +66,6 @@ static const GLfloat tex_coords[] = {
|
|||
};
|
||||
|
||||
static bool keep_aspect = true;
|
||||
static GLuint gl_width = 0, gl_height = 0;
|
||||
typedef struct gl
|
||||
{
|
||||
bool vsync;
|
||||
|
@ -218,19 +219,19 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i
|
|||
if (gl->should_resize)
|
||||
{
|
||||
gl->should_resize = false;
|
||||
SDL_SetVideoMode(gl->win_width, gl->win_height, 32, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0));
|
||||
SDL_SetVideoMode(gl->win_width, gl->win_height, 0, SDL_OPENGL | SDL_RESIZABLE | (g_settings.video.fullscreen ? SDL_FULLSCREEN : 0));
|
||||
set_viewport(gl);
|
||||
}
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl_width, gl_height);
|
||||
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl->vp_width, gl->vp_height);
|
||||
|
||||
if (width != gl->last_width || height != gl->last_height) // res change. need to clear out texture.
|
||||
{
|
||||
gl->last_width = width;
|
||||
gl->last_height = height;
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->tex_w);
|
||||
uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint16_t));
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0, 0, 0, gl->tex_w, gl->tex_h, GL_BGRA,
|
||||
|
@ -302,10 +303,11 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
|||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
|
||||
|
||||
if (!SDL_SetVideoMode(video->width, video->height, 32, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
if (!SDL_SetVideoMode(video->width, video->height, 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
return NULL;
|
||||
|
||||
|
||||
int attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr);
|
||||
if (attr <= 0 && video->vsync)
|
||||
|
@ -315,8 +317,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
|||
if (attr <= 0)
|
||||
SSNES_WARN("GL double buffer has not been enabled!\n");
|
||||
|
||||
// Remove that ugly mouse :D
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
|
||||
gl_t *gl = calloc(1, sizeof(gl_t));
|
||||
if (!gl)
|
||||
|
@ -325,6 +325,18 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
|||
gl->win_width = video->width;
|
||||
gl->win_height = video->height;
|
||||
gl->vsync = video->vsync;
|
||||
set_viewport(gl);
|
||||
|
||||
if (!gl_shader_init())
|
||||
{
|
||||
SSNES_ERR("Shader init failed.\n");
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
free(gl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Remove that ugly mouse :D
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
|
||||
keep_aspect = video->force_aspect;
|
||||
if ( video->smooth )
|
||||
|
@ -332,8 +344,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
|||
else
|
||||
gl->tex_filter = GL_NEAREST;
|
||||
|
||||
set_viewport(gl);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glDisable(GL_DITHER);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
@ -347,6 +357,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
|||
|
||||
glGenTextures(1, &gl->texture);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
|
@ -371,8 +382,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
|||
gl->last_width = gl->tex_w;
|
||||
gl->last_height = gl->tex_h;
|
||||
|
||||
gl_shader_init();
|
||||
|
||||
// Hook up SDL input driver to get SDL_QUIT events and RESIZE.
|
||||
sdl_input_t *sdl_input = input_sdl.init();
|
||||
if (sdl_input)
|
||||
|
@ -386,6 +395,13 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
|||
}
|
||||
else
|
||||
*input = NULL;
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
free(gl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return gl;
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
258
input/sdl.c
258
input/sdl.c
|
@ -34,29 +34,43 @@ static void* sdl_input_init(void)
|
|||
if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
|
||||
return NULL;
|
||||
|
||||
SDL_JoystickEventState(SDL_IGNORE);
|
||||
sdl->num_joysticks = SDL_NumJoysticks();
|
||||
if (sdl->num_joysticks > 2)
|
||||
sdl->num_joysticks = 2;
|
||||
for (unsigned i = 0; i < sdl->num_joysticks; i++)
|
||||
{
|
||||
sdl->joysticks[i] = SDL_JoystickOpen(i);
|
||||
if (!sdl->joysticks[i])
|
||||
{
|
||||
SSNES_ERR("Couldn't open SDL joystick %d\n", i);
|
||||
free(sdl);
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SSNES_LOG("Opened Joystick: %s\n", SDL_JoystickName(i));
|
||||
sdl->num_axes[i] = SDL_JoystickNumAxes(sdl->joysticks[i]);
|
||||
sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]);
|
||||
for (unsigned i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
if (g_settings.input.joypad_map[i] == SSNES_NO_JOYPAD)
|
||||
continue;
|
||||
|
||||
if (sdl->num_joysticks > g_settings.input.joypad_map[i])
|
||||
{
|
||||
sdl->joysticks[i] = SDL_JoystickOpen(g_settings.input.joypad_map[i]);
|
||||
if (!sdl->joysticks[i])
|
||||
{
|
||||
SSNES_ERR("Couldn't open SDL joystick #%u on SNES port %u\n", g_settings.input.joypad_map[i], i + 1);
|
||||
free(sdl);
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SSNES_LOG("Opened Joystick: %s #%u on port %u\n",
|
||||
SDL_JoystickName(g_settings.input.joypad_map[i]), g_settings.input.joypad_map[i], i + 1);
|
||||
sdl->num_axes[i] = SDL_JoystickNumAxes(sdl->joysticks[i]);
|
||||
sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]);
|
||||
sdl->num_hats[i] = SDL_JoystickNumHats(sdl->joysticks[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_WARN("Desired SDL joystick #%u on port %u, but SDL can only detect %u joysticks ...\n",
|
||||
g_settings.input.joypad_map[i], i + 1, sdl->num_joysticks);
|
||||
}
|
||||
}
|
||||
|
||||
return sdl;
|
||||
}
|
||||
|
||||
static bool sdl_key_pressed(void *data, int key)
|
||||
|
||||
static bool sdl_key_pressed(int key)
|
||||
{
|
||||
int num_keys;
|
||||
Uint8 *keymap = SDL_GetKeyState(&num_keys);
|
||||
|
@ -67,27 +81,60 @@ static bool sdl_key_pressed(void *data, int key)
|
|||
return keymap[key];
|
||||
}
|
||||
|
||||
static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key)
|
||||
static bool sdl_joykey_pressed(sdl_input_t *sdl, int port_num, uint16_t joykey)
|
||||
{
|
||||
if (sdl_key_pressed(sdl, key->key))
|
||||
return true;
|
||||
if (port_num >= sdl->num_joysticks)
|
||||
return false;
|
||||
if (key->joykey < sdl->num_buttons[port_num] && SDL_JoystickGetButton(sdl->joysticks[port_num], key->joykey))
|
||||
return true;
|
||||
|
||||
if (key->joyaxis != AXIS_NONE)
|
||||
// Check hat.
|
||||
if (GET_HAT_DIR(joykey))
|
||||
{
|
||||
if (AXIS_NEG_GET(key->joyaxis) < sdl->num_axes[port_num])
|
||||
int hat = GET_HAT(joykey);
|
||||
if (hat < sdl->num_hats[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(key->joyaxis));
|
||||
Uint8 dir = SDL_JoystickGetHat(sdl->joysticks[port_num], hat);
|
||||
switch (GET_HAT_DIR(joykey))
|
||||
{
|
||||
case HAT_UP_MASK:
|
||||
if (dir & SDL_HAT_UP)
|
||||
return true;
|
||||
break;
|
||||
case HAT_DOWN_MASK:
|
||||
if (dir & SDL_HAT_DOWN)
|
||||
return true;
|
||||
break;
|
||||
case HAT_LEFT_MASK:
|
||||
if (dir & SDL_HAT_LEFT)
|
||||
return true;
|
||||
break;
|
||||
case HAT_RIGHT_MASK:
|
||||
if (dir & SDL_HAT_RIGHT)
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Check the button
|
||||
{
|
||||
if (joykey < sdl->num_buttons[port_num] && SDL_JoystickGetButton(sdl->joysticks[port_num], joykey))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sdl_axis_pressed(sdl_input_t *sdl, int port_num, uint32_t joyaxis)
|
||||
{
|
||||
if (joyaxis != AXIS_NONE)
|
||||
{
|
||||
if (AXIS_NEG_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled < -g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
if (AXIS_POS_GET(key->joyaxis) < sdl->num_axes[port_num])
|
||||
if (AXIS_POS_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(key->joyaxis));
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled > g_settings.input.axis_threshold)
|
||||
return true;
|
||||
|
@ -97,45 +144,166 @@ static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_key
|
|||
return false;
|
||||
}
|
||||
|
||||
static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id)
|
||||
static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key)
|
||||
{
|
||||
sdl_input_t *sdl = data;
|
||||
if (device != SNES_DEVICE_JOYPAD)
|
||||
return 0;
|
||||
if (sdl_key_pressed(key->key))
|
||||
return true;
|
||||
if (sdl->joysticks[port_num] == NULL)
|
||||
return false;
|
||||
if (sdl_joykey_pressed(sdl, port_num, key->joykey))
|
||||
return true;
|
||||
if (sdl_axis_pressed(sdl, port_num, key->joyaxis))
|
||||
return true;
|
||||
|
||||
const struct snes_keybind *snes_keybinds = binds[port == SNES_PORT_1 ? 0 : 1];
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sdl_bind_button_pressed(void *data, int key)
|
||||
{
|
||||
// Only let player 1 use special binds called from main loop.
|
||||
const struct snes_keybind *binds = g_settings.input.binds[0];
|
||||
for (int i = 0; binds[i].id != -1; i++)
|
||||
{
|
||||
if (binds[i].id == key)
|
||||
return sdl_is_pressed(data, 0, &binds[i]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int16_t sdl_joypad_device_state(sdl_input_t *sdl, const struct snes_keybind **binds,
|
||||
int port_num, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
const struct snes_keybind *snes_keybinds = binds[port_num];
|
||||
|
||||
// Checks if button is pressed, and sets fast-forwarding state
|
||||
bool pressed = false;
|
||||
int port_num = port == SNES_PORT_1 ? 0 : 1;
|
||||
for (int i = 0; snes_keybinds[i].id != -1; i++)
|
||||
{
|
||||
if (snes_keybinds[i].id == SSNES_FAST_FORWARD_KEY)
|
||||
set_fast_forward_button(sdl_is_pressed(sdl, port_num, &snes_keybinds[i]));
|
||||
else if (!pressed && snes_keybinds[i].id == (int)id)
|
||||
pressed = sdl_is_pressed(sdl, port_num, &snes_keybinds[i]);
|
||||
if (snes_keybinds[i].id == (int)id)
|
||||
return sdl_is_pressed(sdl, port_num, &snes_keybinds[i]);
|
||||
}
|
||||
|
||||
return pressed;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int16_t sdl_mouse_device_state(sdl_input_t *sdl, bool port, unsigned id)
|
||||
{
|
||||
// Might implement support for joypad mapping later.
|
||||
(void)port;
|
||||
switch (id)
|
||||
{
|
||||
case SNES_DEVICE_ID_MOUSE_LEFT:
|
||||
return sdl->mouse_l;
|
||||
case SNES_DEVICE_ID_MOUSE_RIGHT:
|
||||
return sdl->mouse_r;
|
||||
case SNES_DEVICE_ID_MOUSE_X:
|
||||
return sdl->mouse_x;
|
||||
case SNES_DEVICE_ID_MOUSE_Y:
|
||||
return sdl->mouse_y;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Missing some controllers, but hey :)
|
||||
static int16_t sdl_scope_device_state(sdl_input_t *sdl, unsigned id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case SNES_DEVICE_ID_SUPER_SCOPE_X:
|
||||
return sdl->mouse_x;
|
||||
case SNES_DEVICE_ID_SUPER_SCOPE_Y:
|
||||
return sdl->mouse_y;
|
||||
case SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER:
|
||||
return sdl->mouse_l;
|
||||
case SNES_DEVICE_ID_SUPER_SCOPE_CURSOR:
|
||||
return sdl->mouse_m;
|
||||
case SNES_DEVICE_ID_SUPER_SCOPE_TURBO:
|
||||
return sdl->mouse_r;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Support two players.
|
||||
static int16_t sdl_justifier_device_state(sdl_input_t *sdl, unsigned index, unsigned id)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case SNES_DEVICE_ID_JUSTIFIER_X:
|
||||
return sdl->mouse_x;
|
||||
case SNES_DEVICE_ID_JUSTIFIER_Y:
|
||||
return sdl->mouse_y;
|
||||
case SNES_DEVICE_ID_JUSTIFIER_TRIGGER:
|
||||
return sdl->mouse_l;
|
||||
case SNES_DEVICE_ID_JUSTIFIER_START:
|
||||
return sdl->mouse_r;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
switch (device)
|
||||
{
|
||||
case SNES_DEVICE_JOYPAD:
|
||||
return sdl_joypad_device_state(data, binds, port == SNES_PORT_1 ? 0 : 1, device, index, id);
|
||||
case SNES_DEVICE_MULTITAP:
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_2) ? 1 + index : 0, device, index, id);
|
||||
case SNES_DEVICE_MOUSE:
|
||||
return sdl_mouse_device_state(data, port, id);
|
||||
case SNES_DEVICE_SUPER_SCOPE:
|
||||
return sdl_scope_device_state(data, id);
|
||||
case SNES_DEVICE_JUSTIFIER:
|
||||
case SNES_DEVICE_JUSTIFIERS:
|
||||
return sdl_justifier_device_state(data, index, id);
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void sdl_input_free(void *data)
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
// Flush out all pending events.
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event));
|
||||
|
||||
sdl_input_t *sdl = data;
|
||||
for (int i = 0; i < sdl->num_joysticks; i++)
|
||||
SDL_JoystickClose(sdl->joysticks[i]);
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
if (sdl->joysticks[i])
|
||||
SDL_JoystickClose(sdl->joysticks[i]);
|
||||
}
|
||||
|
||||
free(data);
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdl_poll_mouse(sdl_input_t *sdl)
|
||||
{
|
||||
int _x, _y;
|
||||
Uint8 btn = SDL_GetRelativeMouseState(&_x, &_y);
|
||||
sdl->mouse_x = _x;
|
||||
sdl->mouse_y = _y;
|
||||
sdl->mouse_l = SDL_BUTTON(SDL_BUTTON_LEFT) & btn ? 1 : 0;
|
||||
sdl->mouse_r = SDL_BUTTON(SDL_BUTTON_RIGHT) & btn ? 1 : 0;
|
||||
sdl->mouse_m = SDL_BUTTON(SDL_BUTTON_MIDDLE) & btn ? 1 : 0;
|
||||
}
|
||||
|
||||
static void sdl_input_poll(void *data)
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
SDL_Event event;
|
||||
SDL_JoystickUpdate();
|
||||
sdl_poll_mouse(data);
|
||||
|
||||
sdl_input_t *sdl = data;
|
||||
// Search for events...
|
||||
|
@ -170,7 +338,7 @@ const input_driver_t input_sdl = {
|
|||
.init = sdl_input_init,
|
||||
.poll = sdl_input_poll,
|
||||
.input_state = sdl_input_state,
|
||||
.key_pressed = sdl_key_pressed,
|
||||
.key_pressed = sdl_bind_button_pressed,
|
||||
.free = sdl_input_free,
|
||||
.ident = "sdl"
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
232
settings.c
232
settings.c
|
@ -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
408
ssnes.c
|
@ -36,14 +36,11 @@
|
|||
struct global g_extern = {
|
||||
.video_active = true,
|
||||
.audio_active = true,
|
||||
.game_type = SSNES_CART_NORMAL,
|
||||
};
|
||||
|
||||
// To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then.
|
||||
|
||||
#define AUDIO_CHUNK_SIZE_BLOCKING 64
|
||||
#define AUDIO_CHUNK_SIZE_NONBLOCKING 2048 // So we don't get complete line-noise when fast-forwarding audio.
|
||||
static size_t audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING;
|
||||
void set_fast_forward_button(bool new_button_state)
|
||||
static void set_fast_forward_button(bool new_button_state)
|
||||
{
|
||||
static bool old_button_state = false;
|
||||
static bool syncing_state = false;
|
||||
|
@ -54,10 +51,11 @@ void set_fast_forward_button(bool new_button_state)
|
|||
driver.video->set_nonblock_state(driver.video_data, syncing_state);
|
||||
if (g_extern.audio_active)
|
||||
driver.audio->set_nonblock_state(driver.audio_data, (g_settings.audio.sync) ? syncing_state : true);
|
||||
|
||||
if (syncing_state)
|
||||
audio_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING;
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.nonblock_chunk_size;
|
||||
else
|
||||
audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING;
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
|
||||
}
|
||||
old_button_state = new_button_state;
|
||||
}
|
||||
|
@ -102,7 +100,9 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
|||
#ifdef HAVE_FILTER
|
||||
uint16_t output_filter[width * height * 4 * 4];
|
||||
uint16_t output[width * height];
|
||||
process_frame(output, data, width, height);
|
||||
|
||||
if (g_settings.video.filter != FILTER_NONE)
|
||||
process_frame(output, data, width, height);
|
||||
|
||||
switch (g_settings.video.filter)
|
||||
{
|
||||
|
@ -160,37 +160,42 @@ static void audio_sample(uint16_t left, uint16_t right)
|
|||
}
|
||||
#endif
|
||||
|
||||
static float data[AUDIO_CHUNK_SIZE_NONBLOCKING];
|
||||
static int data_ptr = 0;
|
||||
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(*(int16_t*)&left)/0x8000;
|
||||
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(*(int16_t*)&right)/0x8000;
|
||||
|
||||
data[data_ptr++] = (float)(*(int16_t*)&left)/0x7FFF;
|
||||
data[data_ptr++] = (float)(*(int16_t*)&right)/0x7FFF;
|
||||
|
||||
if ( data_ptr >= audio_chunk_size )
|
||||
if (g_extern.audio_data.data_ptr >= g_extern.audio_data.chunk_size)
|
||||
{
|
||||
float outsamples[audio_chunk_size * 16];
|
||||
int16_t temp_outsamples[audio_chunk_size * 16];
|
||||
|
||||
SRC_DATA src_data;
|
||||
|
||||
src_data.data_in = data;
|
||||
src_data.data_out = outsamples;
|
||||
src_data.input_frames = audio_chunk_size / 2;
|
||||
src_data.output_frames = audio_chunk_size * 8;
|
||||
src_data.data_in = g_extern.audio_data.data;
|
||||
src_data.data_out = g_extern.audio_data.outsamples;
|
||||
src_data.input_frames = g_extern.audio_data.chunk_size / 2;
|
||||
src_data.output_frames = g_extern.audio_data.chunk_size * 8;
|
||||
src_data.end_of_input = 0;
|
||||
src_data.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate;
|
||||
|
||||
src_process(g_extern.source, &src_data);
|
||||
|
||||
src_float_to_short_array(outsamples, temp_outsamples, src_data.output_frames_gen * 2);
|
||||
|
||||
if ( driver.audio->write(driver.audio_data, temp_outsamples, src_data.output_frames_gen * 4) < 0 )
|
||||
if (driver.audio->float_samples)
|
||||
{
|
||||
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
|
||||
g_extern.audio_active = false;
|
||||
if (driver.audio->write(driver.audio_data, g_extern.audio_data.outsamples, src_data.output_frames_gen * sizeof(float) * 2) < 0)
|
||||
{
|
||||
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
|
||||
g_extern.audio_active = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
src_float_to_short_array(g_extern.audio_data.outsamples, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * 2);
|
||||
|
||||
if (driver.audio->write(driver.audio_data, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * sizeof(int16_t) * 2) < 0)
|
||||
{
|
||||
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
|
||||
g_extern.audio_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
data_ptr = 0;
|
||||
g_extern.audio_data.data_ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,7 +206,10 @@ static void input_poll(void)
|
|||
|
||||
static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
const struct snes_keybind *binds[] = { g_settings.input.binds[0], g_settings.input.binds[1] };
|
||||
const struct snes_keybind *binds[MAX_PLAYERS];
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
binds[i] = g_settings.input.binds[i];
|
||||
|
||||
return driver.input->input_state(driver.input_data, binds, port, device, index, id);
|
||||
}
|
||||
|
||||
|
@ -234,10 +242,22 @@ static void print_help(void)
|
|||
puts("=================================================");
|
||||
puts("ssnes: Simple Super Nintendo Emulator (libsnes)");
|
||||
puts("=================================================");
|
||||
puts("Usage: ssnes [rom file] [-h/--help | -s/--save" FFMPEG_HELP_QUARK "]");
|
||||
puts("Usage: ssnes [rom file] [-h/--help | -c/--config | -v/--verbose | -4/--multitap | -j/--justifier | -J/--justifiers | -S/--savestate | -m/--mouse | -g/--gameboy | -b/--bsx | -B/--bsxslot | --sufamiA | --sufamiB | -p/--scope | -s/--save" FFMPEG_HELP_QUARK "]");
|
||||
puts("\t-h/--help: Show this help message");
|
||||
puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin");
|
||||
puts("\t-S/--savestate: Path to use for save states. If not selected, *.state will be assumed.");
|
||||
puts("\t-c/--config: Path for config file." SSNES_DEFAULT_CONF_PATH_STR);
|
||||
puts("\t-g/--gameboy: Path to Gameboy ROM. Load SuperGameBoy as the regular rom.");
|
||||
puts("\t-b/--bsx: Path to BSX rom. Load BSX BIOS as the regular rom.");
|
||||
puts("\t-B/--bsxslot: Path to BSX slotted rom. Load BSX BIOS as the regular rom.");
|
||||
puts("\t--sufamiA: Path to A slot of Sufami Turbo. Load Sufami base cart as regular rom.");
|
||||
puts("\t--sufamiB: Path to B slot of Sufami Turbo.");
|
||||
puts("\t-m/--mouse: Connect a virtual mouse into designated port of the SNES (1 or 2).");
|
||||
puts("\t\tThis argument can be specified several times to connect more mice.");
|
||||
puts("\t-p/--scope: Connect a virtual SuperScope into port 2 of the SNES.");
|
||||
puts("\t-j/--justifier: Connect a virtual Konami Justifier into port 2 of the SNES.");
|
||||
puts("\t-J/--justifiers: Daisy chain two virtual Konami Justifiers into port 2 of the SNES.");
|
||||
puts("\t-4/--multitap: Connect a multitap to port 2 of the SNES.");
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file.");
|
||||
|
@ -260,7 +280,18 @@ static void parse_input(int argc, char *argv[])
|
|||
{ "record", 1, NULL, 'r' },
|
||||
#endif
|
||||
{ "verbose", 0, NULL, 'v' },
|
||||
{ "gameboy", 1, NULL, 'g' },
|
||||
{ "config", 0, NULL, 'c' },
|
||||
{ "mouse", 1, NULL, 'm' },
|
||||
{ "scope", 0, NULL, 'p' },
|
||||
{ "savestate", 1, NULL, 'S' },
|
||||
{ "bsx", 1, NULL, 'b' },
|
||||
{ "bsxslot", 1, NULL, 'B' },
|
||||
{ "justifier", 0, NULL, 'j' },
|
||||
{ "justifiers", 0, NULL, 'J' },
|
||||
{ "multitap", 0, NULL, '4' },
|
||||
{ "sufamiA", 1, NULL, 'Y' },
|
||||
{ "sufamiB", 1, NULL, 'Z' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
|
@ -272,10 +303,11 @@ static void parse_input(int argc, char *argv[])
|
|||
#define FFMPEG_RECORD_ARG
|
||||
#endif
|
||||
|
||||
char optstring[] = "hs:vc:" FFMPEG_RECORD_ARG;
|
||||
char optstring[] = "hs:vc:S:m:p4jJg:b:B:Y:Z:" FFMPEG_RECORD_ARG;
|
||||
for(;;)
|
||||
{
|
||||
int c = getopt_long(argc, argv, optstring, opts, &option_index);
|
||||
int port;
|
||||
|
||||
if (c == -1)
|
||||
break;
|
||||
|
@ -286,15 +318,70 @@ static void parse_input(int argc, char *argv[])
|
|||
print_help();
|
||||
exit(0);
|
||||
|
||||
case '4':
|
||||
g_extern.has_multitap = true;
|
||||
break;
|
||||
|
||||
case 'j':
|
||||
g_extern.has_justifier = true;
|
||||
break;
|
||||
|
||||
case 'J':
|
||||
g_extern.has_justifiers = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm));
|
||||
g_extern.savefile_name_srm[sizeof(g_extern.savefile_name_srm)-1] = '\0';
|
||||
strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm) - 1);
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
strncpy(g_extern.gb_rom_path, optarg, sizeof(g_extern.gb_rom_path) - 1);
|
||||
g_extern.game_type = SSNES_CART_SGB;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1);
|
||||
g_extern.game_type = SSNES_CART_BSX;
|
||||
break;
|
||||
|
||||
case 'B':
|
||||
strncpy(g_extern.bsx_rom_path, optarg, sizeof(g_extern.bsx_rom_path) - 1);
|
||||
g_extern.game_type = SSNES_CART_BSX_SLOTTED;
|
||||
break;
|
||||
|
||||
case 'Y':
|
||||
strncpy(g_extern.sufami_rom_path[0], optarg, sizeof(g_extern.sufami_rom_path[0]) - 1);
|
||||
g_extern.game_type = SSNES_CART_SUFAMI;
|
||||
break;
|
||||
|
||||
case 'Z':
|
||||
strncpy(g_extern.sufami_rom_path[1], optarg, sizeof(g_extern.sufami_rom_path[1]) - 1);
|
||||
g_extern.game_type = SSNES_CART_SUFAMI;
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
strncpy(g_extern.savestate_name, optarg, sizeof(g_extern.savestate_name) - 1);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
g_extern.verbose = true;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
port = strtol(optarg, NULL, 0);
|
||||
if (port < 1 || port > 2)
|
||||
{
|
||||
SSNES_ERR("Connect mouse to port 1 or 2.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
g_extern.has_mouse[port - 1] = true;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
g_extern.has_scope[1] = true;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1);
|
||||
break;
|
||||
|
@ -329,76 +416,129 @@ static void parse_input(int argc, char *argv[])
|
|||
g_extern.rom_file = fopen(argv[optind], "rb");
|
||||
if (g_extern.rom_file == NULL)
|
||||
{
|
||||
SSNES_ERR("Could not open file: \"%s\"\n", optarg);
|
||||
SSNES_ERR("Could not open file: \"%s\"\n", argv[optind]);
|
||||
exit(1);
|
||||
}
|
||||
// strl* would be nice :D
|
||||
if (strlen(g_extern.savefile_name_srm) == 0)
|
||||
fill_pathname(g_extern.savefile_name_srm, argv[optind], ".srm");
|
||||
{
|
||||
strcpy(g_extern.savefile_name_srm, g_extern.basename);
|
||||
size_t len = strlen(g_extern.savefile_name_srm);
|
||||
strncat(g_extern.savefile_name_srm, ".srm", sizeof(g_extern.savefile_name_srm) - len - 1);
|
||||
}
|
||||
if (strlen(g_extern.savestate_name) == 0)
|
||||
{
|
||||
strcpy(g_extern.savestate_name, g_extern.basename);
|
||||
size_t len = strlen(g_extern.savestate_name);
|
||||
strncat(g_extern.savestate_name, ".state", sizeof(g_extern.savestate_name) - len - 1);
|
||||
}
|
||||
}
|
||||
else if (strlen(g_extern.savefile_name_srm) == 0)
|
||||
{
|
||||
SSNES_ERR("Need savefile argument when reading rom from stdin.\n");
|
||||
SSNES_ERR("Need savefile path argument (--save) when reading rom from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
else if (strlen(g_extern.savestate_name) == 0)
|
||||
{
|
||||
SSNES_ERR("Need savestate path argument (--savefile) when reading rom from stdin.\n");
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
// TODO: Add rest of the controllers.
|
||||
static void init_controllers(void)
|
||||
{
|
||||
parse_input(argc, argv);
|
||||
parse_config();
|
||||
init_dlsym();
|
||||
|
||||
psnes_init();
|
||||
if (strlen(g_extern.basename) > 0)
|
||||
psnes_set_cartridge_basename(g_extern.basename);
|
||||
|
||||
SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor());
|
||||
void *rom_buf;
|
||||
ssize_t rom_len = 0;
|
||||
if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1)
|
||||
if (g_extern.has_justifier)
|
||||
{
|
||||
SSNES_ERR("Could not read ROM file.\n");
|
||||
exit(1);
|
||||
SSNES_LOG("Connecting Justifier to port 2.\n");
|
||||
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_JUSTIFIER);
|
||||
}
|
||||
SSNES_LOG("ROM size: %d bytes\n", (int)rom_len);
|
||||
|
||||
if (g_extern.rom_file != NULL)
|
||||
fclose(g_extern.rom_file);
|
||||
|
||||
char statefile_name[strlen(g_extern.savefile_name_srm)+strlen(".state")+1];
|
||||
char savefile_name_rtc[strlen(g_extern.savefile_name_srm)+strlen(".rtc")+1];
|
||||
|
||||
fill_pathname(statefile_name, argv[1], ".state");
|
||||
fill_pathname(savefile_name_rtc, argv[1], ".rtc");
|
||||
|
||||
init_drivers();
|
||||
|
||||
psnes_set_video_refresh(video_frame);
|
||||
psnes_set_audio_sample(audio_sample);
|
||||
psnes_set_input_poll(input_poll);
|
||||
psnes_set_input_state(input_state);
|
||||
|
||||
if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len))
|
||||
else if (g_extern.has_justifiers)
|
||||
{
|
||||
SSNES_ERR("ROM file is not valid!\n");
|
||||
goto error;
|
||||
SSNES_LOG("Connecting Justifiers to port 2.\n");
|
||||
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_JUSTIFIERS);
|
||||
}
|
||||
|
||||
free(rom_buf);
|
||||
|
||||
unsigned serial_size = psnes_serialize_size();
|
||||
uint8_t *serial_data = malloc(serial_size);
|
||||
if (serial_data == NULL)
|
||||
else if (g_extern.has_multitap)
|
||||
{
|
||||
SSNES_ERR("Failed to allocate memory for states!\n");
|
||||
goto error;
|
||||
SSNES_LOG("Connecting multitap to port 2.\n");
|
||||
psnes_set_controller_port_device(SNES_PORT_2, SNES_DEVICE_MULTITAP);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (g_extern.has_mouse[i])
|
||||
{
|
||||
SSNES_LOG("Connecting mouse to port %d\n", i + 1);
|
||||
psnes_set_controller_port_device(i, SNES_DEVICE_MOUSE);
|
||||
}
|
||||
else if (g_extern.has_scope[i])
|
||||
{
|
||||
SSNES_LOG("Connecting scope to port %d\n", i + 1);
|
||||
psnes_set_controller_port_device(i, SNES_DEVICE_SUPER_SCOPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void load_save_files(void)
|
||||
{
|
||||
switch (g_extern.game_type)
|
||||
{
|
||||
case SSNES_CART_NORMAL:
|
||||
case SSNES_CART_SGB:
|
||||
load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
|
||||
load_save_file(g_extern.savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
|
||||
break;
|
||||
|
||||
case SSNES_CART_BSX:
|
||||
case SSNES_CART_BSX_SLOTTED:
|
||||
load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
|
||||
load_save_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM);
|
||||
break;
|
||||
|
||||
case SSNES_CART_SUFAMI:
|
||||
load_save_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM);
|
||||
load_save_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void save_files(void)
|
||||
{
|
||||
switch (g_extern.game_type)
|
||||
{
|
||||
case SSNES_CART_NORMAL:
|
||||
case SSNES_CART_SGB:
|
||||
save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
|
||||
save_file(g_extern.savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
|
||||
break;
|
||||
|
||||
case SSNES_CART_BSX:
|
||||
case SSNES_CART_BSX_SLOTTED:
|
||||
save_file(g_extern.savefile_name_srm, SNES_MEMORY_BSX_RAM);
|
||||
save_file(g_extern.savefile_name_psrm, SNES_MEMORY_BSX_PRAM);
|
||||
break;
|
||||
|
||||
case SSNES_CART_SUFAMI:
|
||||
save_file(g_extern.savefile_name_asrm, SNES_MEMORY_SUFAMI_TURBO_A_RAM);
|
||||
save_file(g_extern.savefile_name_bsrm, SNES_MEMORY_SUFAMI_TURBO_B_RAM);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
|
||||
load_save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
static void init_recording(void)
|
||||
{
|
||||
// Hardcode these options at the moment. Should be specificed in the config file later on.
|
||||
if (g_extern.recording)
|
||||
{
|
||||
|
@ -413,7 +553,7 @@ int main(int argc, char *argv[])
|
|||
.channels = 2,
|
||||
.samplerate = 32040,
|
||||
.filename = g_extern.record_path,
|
||||
.fps = snes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
|
||||
.fps = psnes_get_region() == SNES_REGION_NTSC ? ntsc_fps : pal_fps,
|
||||
.aspect_ratio = 4.0/3
|
||||
};
|
||||
SSNES_LOG("Recording with FFmpeg to %s.\n", g_extern.record_path);
|
||||
|
@ -424,42 +564,116 @@ int main(int argc, char *argv[])
|
|||
g_extern.recording = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void deinit_recording(void)
|
||||
{
|
||||
if (g_extern.recording)
|
||||
{
|
||||
ffemu_finalize(g_extern.rec);
|
||||
ffemu_free(g_extern.rec);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void fill_pathnames(void)
|
||||
{
|
||||
switch (g_extern.game_type)
|
||||
{
|
||||
case SSNES_CART_BSX:
|
||||
case SSNES_CART_BSX_SLOTTED:
|
||||
// BSX PSRM
|
||||
fill_pathname(g_extern.savefile_name_psrm, g_extern.savefile_name_srm, ".psrm");
|
||||
break;
|
||||
|
||||
case SSNES_CART_SUFAMI:
|
||||
// SUFAMI ARAM
|
||||
fill_pathname(g_extern.savefile_name_asrm, g_extern.savefile_name_srm, ".asrm");
|
||||
// SUFAMI BRAM
|
||||
fill_pathname(g_extern.savefile_name_bsrm, g_extern.savefile_name_srm, ".bsrm");
|
||||
break;
|
||||
|
||||
default:
|
||||
// Infer .rtc save path from save ram path.
|
||||
fill_pathname(g_extern.savefile_name_rtc, g_extern.savefile_name_srm, ".rtc");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
parse_input(argc, argv);
|
||||
parse_config();
|
||||
init_dlsym();
|
||||
|
||||
psnes_init();
|
||||
if (strlen(g_extern.basename) > 0)
|
||||
psnes_set_cartridge_basename(g_extern.basename);
|
||||
|
||||
SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor());
|
||||
|
||||
fill_pathnames();
|
||||
|
||||
if (!init_rom_file(g_extern.game_type))
|
||||
goto error;
|
||||
|
||||
init_drivers();
|
||||
|
||||
psnes_set_video_refresh(video_frame);
|
||||
psnes_set_audio_sample(audio_sample);
|
||||
psnes_set_input_poll(input_poll);
|
||||
psnes_set_input_state(input_state);
|
||||
|
||||
init_controllers();
|
||||
|
||||
unsigned serial_size = psnes_serialize_size();
|
||||
uint8_t *serial_data = malloc(serial_size);
|
||||
if (serial_data == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to allocate memory for states!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
load_save_files();
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
init_recording();
|
||||
#endif
|
||||
|
||||
// Main loop
|
||||
for(;;)
|
||||
{
|
||||
if (driver.input->key_pressed(driver.input_data, g_settings.input.exit_emulator_key) ||
|
||||
// Time to drop?
|
||||
if (driver.input->key_pressed(driver.input_data, SSNES_QUIT_KEY) ||
|
||||
!driver.video->alive(driver.video_data))
|
||||
break;
|
||||
|
||||
if (driver.input->key_pressed(driver.input_data, g_settings.input.save_state_key))
|
||||
{
|
||||
write_file(statefile_name, serial_data, serial_size);
|
||||
}
|
||||
set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY));
|
||||
|
||||
else if (driver.input->key_pressed(driver.input_data, g_settings.input.load_state_key))
|
||||
load_state(statefile_name, serial_data, serial_size);
|
||||
// Save or load state here.
|
||||
if (driver.input->key_pressed(driver.input_data, SSNES_SAVE_STATE_KEY))
|
||||
write_file(g_extern.savestate_name, serial_data, serial_size);
|
||||
else if (driver.input->key_pressed(driver.input_data, SSNES_LOAD_STATE_KEY))
|
||||
load_state(g_extern.savestate_name, serial_data, serial_size);
|
||||
|
||||
else if (driver.input->key_pressed(driver.input_data, g_settings.input.toggle_fullscreen_key))
|
||||
// If we go fullscreen we drop all drivers and reinit to be safe.
|
||||
else if (driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY))
|
||||
{
|
||||
g_settings.video.fullscreen = !g_settings.video.fullscreen;
|
||||
uninit_drivers();
|
||||
init_drivers();
|
||||
}
|
||||
|
||||
// Run libsnes for one frame.
|
||||
psnes_run();
|
||||
}
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording)
|
||||
{
|
||||
ffemu_finalize(g_extern.rec);
|
||||
ffemu_free(g_extern.rec);
|
||||
}
|
||||
deinit_recording();
|
||||
#endif
|
||||
|
||||
save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM);
|
||||
save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC);
|
||||
// Flush out SRAM (and RTC)
|
||||
save_files();
|
||||
|
||||
psnes_unload_cartridge();
|
||||
psnes_term();
|
||||
|
|
19
ssnes.cfg
19
ssnes.cfg
|
@ -88,7 +88,13 @@
|
|||
# input_player1_up = up
|
||||
# input_player1_down = down
|
||||
|
||||
# If desired, it is possible to override which joypads are being used for player 1 and 2. First joypad available is 0.
|
||||
# input_player1_joypad_index = 0
|
||||
# input_player2_joypad_index = 1
|
||||
|
||||
# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output.
|
||||
# You can use joypad hats with hnxx, where n is the hat, and xx is a string representing direction.
|
||||
# E.g. "h0up"
|
||||
# input_player1_a_btn = 1
|
||||
# input_player1_b_btn = 0
|
||||
# input_player1_y_btn = 2
|
||||
|
@ -102,7 +108,9 @@
|
|||
# input_player1_up_btn = 13
|
||||
# input_player1_down_btn = 14
|
||||
|
||||
# Axis for DPAD. Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number.
|
||||
# Axis for DPAD.
|
||||
# Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number.
|
||||
# Do note that every other input option has the corresponding _btn and _axis binds as well; they are omitted here for clarity.
|
||||
# input_player1_left_axis = -0
|
||||
# input_player1_right_axis = +0
|
||||
# input_player1_up_axis = +1
|
||||
|
@ -138,8 +146,8 @@
|
|||
|
||||
# input_player2_left_axis = -0
|
||||
# input_player2_right_axis = +0
|
||||
# input_player2_up_axis = +1
|
||||
# input_player2_down_axis = -1
|
||||
# input_player2_up_axis = -1
|
||||
# input_player2_down_axis = +1
|
||||
|
||||
# Toggles fullscreen.
|
||||
# input_toggle_fullscreen = f
|
||||
|
@ -150,8 +158,7 @@
|
|||
|
||||
# Toggles between fast-forwarding and normal speed.
|
||||
# input_toggle_fast_forward = space
|
||||
# Same, just mapping to a joypad button.
|
||||
# input_toggle_fast_forward_btn = 10
|
||||
|
||||
# Key to exit emulator cleanly.
|
||||
# Key to exit emulator cleanly.
|
||||
# Killing it in any hard way (SIGTERM, SIGKILL, etc, will terminate emulator without saving RAM, etc.)
|
||||
# input_exit_emulator = escape
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
int real_main(int argc, char *argv[]);
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
return real_main(argc, argv);
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue